I'm using the SKBitmap.Resize() method in SkiaSharp on a Xamarin.Forms project to resize images for display. The problem I'm encountering is when taking a photo on iOS, when a photo is taken in portrait, the image is displayed with the right side up. Taking a photo on Android, importing from the photo gallery on both an Android and iOS device maintains orientation, but taking a photo in iOS does not. If I don't resize the image using SkiaSharp (just display the image without any resizing), then the image displays with the proper orientation. However that is not a solution as the images need to be resized. Below is my code -
private byte[] GetResizedImageData(string imageName)
{
float resizeFactor = 0.5f;
var filePath = PathUtil.GetImagePath(imageName);
var ogBitmap = SKBitmap.Decode(filePath);
float fWidth = ogBitmap.Width * resizeFactor;
int width = (int) Math.Round(fWidth);
float fHeight = ogBitmap.Height * resizeFactor;
int height = (int) Math.Round(fHeight);
if (height >= 4096 || width >= 4096)
{
width = width * (int)resizeFactor;
height = height * (int)resizeFactor;
}
var scaledBitmap = ogBitmap.Resize(new SKImageInfo( width, height), SKBitmapResizeMethod.Box);
var image = SKImage.FromBitmap(scaledBitmap);
var data = image.Encode(SKEncodedImageFormat.Jpeg, 100);
return data.ToArray();
}
PathUtil.GetImagePath() is just a helper to get platform-specific paths for where the photos are being stored.
For those with the same issue I did the following and would gladly accept input on improvements.
public static SKBitmap HandleOrientation(SKBitmap bitmap, SKCodecOrigin orientation)
{
SKBitmap rotated;
switch (orientation)
{
case SKCodecOrigin.BottomRight:
using (var surface = new SKCanvas(bitmap))
{
surface.RotateDegrees(180, bitmap.Width / 2, bitmap.Height / 2);
surface.DrawBitmap(bitmap.Copy(), 0, 0);
}
return bitmap;
case SKCodecOrigin.RightTop:
rotated = new SKBitmap(bitmap.Height, bitmap.Width);
using (var surface = new SKCanvas(rotated))
{
surface.Translate(rotated.Width, 0);
surface.RotateDegrees(90);
surface.DrawBitmap(bitmap, 0, 0);
}
return rotated;
case SKCodecOrigin.LeftBottom:
rotated = new SKBitmap(bitmap.Height, bitmap.Width);
using (var surface = new SKCanvas(rotated))
{
surface.Translate(0, rotated.Height);
surface.RotateDegrees(270);
surface.DrawBitmap(bitmap, 0, 0);
}
return rotated;
default:
return bitmap;
}
And then use the following to get the original orientation.
// TODO: Improve this.. I do not know how to "reset"
// the inputStream, so new it up twice. :/
using (var inputStream = new SKManagedStream(imageIn))
{
using (var codec = SKCodec.Create(inputStream))
{
orientation = codec.Origin;
}
}
.......
Then
SKBitmap orientedWExif = HandleOrientation(resized, orientation);
SkiaSharp didn't actually provide a method for me to manipulate and change the orientation of the image. In the end I ended up altering the orientation as I captured and saved the image using platform specific code.
Thank you #ttugates Your answer helped me to fix issue with the orientation rotation. Just want to update your answer as there is some deprecated code.
using (var inputStream = new SKManagedStream(await file.ReadAllStreamAsync()))
{
using (var codec = SKCodec.Create(inputStream))
{
orientation = codec.EncodedOrigin;
}
}
public static SKBitmap HandleOrientation(SKBitmap bitmap, SKEncodedOrigin orientation)
{
SKBitmap rotated;
switch (orientation)
{
case SKEncodedOrigin.BottomRight:
using (var surface = new SKCanvas(bitmap))
{
surface.RotateDegrees(180, bitmap.Width / 2, bitmap.Height / 2);
surface.DrawBitmap(bitmap.Copy(), 0, 0);
}
return bitmap;
case SKEncodedOrigin.RightTop:
rotated = new SKBitmap(bitmap.Height, bitmap.Width);
using (var surface = new SKCanvas(rotated))
{
surface.Translate(rotated.Width, 0);
surface.RotateDegrees(90);
surface.DrawBitmap(bitmap, 0, 0);
}
return rotated;
case SKEncodedOrigin.LeftBottom:
rotated = new SKBitmap(bitmap.Height, bitmap.Width);
using (var surface = new SKCanvas(rotated))
{
surface.Translate(0, rotated.Height);
surface.RotateDegrees(270);
surface.DrawBitmap(bitmap, 0, 0);
}
return rotated;
default:
return bitmap;
}
}
Workable solution for resize and handle orientation
public class ImageResizer : IImageResizer
{
private const int Quality = 75;
public byte[] Resize(byte[] data, int newWidth, int newHeight)
{
using (var inputStream = new SKMemoryStream(data))
{
using (var codec = SKCodec.Create(inputStream))
{
using (var original_old = SKBitmap.Decode(codec))
{
int sourceWidth = original_old.Width;
int sourceHeight = original_old.Height;
float nPercentW = ((float) newWidth / (float) sourceWidth);
float nPercentH = ((float) newHeight / (float) sourceHeight);
float nPercent = nPercentH < nPercentW ? nPercentH : nPercentW;
int destWidth = (int) (sourceWidth * nPercent);
int destHeight = (int) (sourceHeight * nPercent);
using (SKBitmap original = original_old.Resize(new SKImageInfo(destWidth, destHeight), SKFilterQuality.Medium))
{
var useWidth = original.Width;
var useHeight = original.Height;
Action<SKCanvas> transform = canvas => { };
switch (codec.EncodedOrigin)
{
case SKEncodedOrigin.TopLeft:
break;
case SKEncodedOrigin.TopRight:
// flip along the x-axis
transform = canvas => canvas.Scale(-1, 1, useWidth / 2, useHeight / 2);
break;
case SKEncodedOrigin.BottomRight:
transform = canvas => canvas.RotateDegrees(180, useWidth / 2, useHeight / 2);
break;
case SKEncodedOrigin.BottomLeft:
// flip along the y-axis
transform = canvas => canvas.Scale(1, -1, useWidth / 2, useHeight / 2);
break;
case SKEncodedOrigin.LeftTop:
useWidth = original.Height;
useHeight = original.Width;
transform = canvas =>
{
// Rotate 90
canvas.RotateDegrees(90, useWidth / 2, useHeight / 2);
canvas.Scale(useHeight * 1.0f / useWidth, -useWidth * 1.0f / useHeight, useWidth / 2, useHeight / 2);
};
break;
case SKEncodedOrigin.RightTop:
useWidth = original.Height;
useHeight = original.Width;
transform = canvas =>
{
// Rotate 90
canvas.RotateDegrees(90, useWidth / 2, useHeight / 2);
canvas.Scale(useHeight * 1.0f / useWidth, useWidth * 1.0f / useHeight, useWidth / 2, useHeight / 2);
};
break;
case SKEncodedOrigin.RightBottom:
useWidth = original.Height;
useHeight = original.Width;
transform = canvas =>
{
// Rotate 90
canvas.RotateDegrees(90, useWidth / 2, useHeight / 2);
canvas.Scale(-useHeight * 1.0f / useWidth, useWidth * 1.0f / useHeight, useWidth / 2, useHeight / 2);
};
break;
case SKEncodedOrigin.LeftBottom:
useWidth = original.Height;
useHeight = original.Width;
transform = canvas =>
{
// Rotate 90
canvas.RotateDegrees(90, useWidth / 2, useHeight / 2);
canvas.Scale(-useHeight * 1.0f / useWidth, -useWidth * 1.0f / useHeight, useWidth / 2, useHeight / 2);
};
break;
}
var info = new SKImageInfo(useWidth, useHeight);
using (var surface = SKSurface.Create(info))
{
using (var paint = new SKPaint())
{
// high quality with antialiasing
paint.IsAntialias = true;
paint.FilterQuality = SKFilterQuality.High;
// rotate according to origin
transform.Invoke(surface.Canvas);
// draw the bitmap to fill the surface
surface.Canvas.DrawBitmap(original, info.Rect, paint);
surface.Canvas.Flush();
using (SKImage image = surface.Snapshot())
{
return image.Encode(SKEncodedImageFormat.Jpeg, Quality).ToArray();
}
}
}
}
}
}
}
}
}
Related
I am working on xamarin.forms. I have to select images from gallery and then resize them and then upload them on server. But I don't know how I can resize selected image in a given particular size?
Please update me how I can do this?
This can be used with a stream (if you're using the Media Plugin https://github.com/jamesmontemagno/MediaPlugin) or standard byte arrays.
// If you already have the byte[]
byte[] resizedImage = await CrossImageResizer.Current.ResizeImageWithAspectRatioAsync(originalImageBytes, 500, 1000);
// If you have a stream, such as:
// var file = await CrossMedia.Current.PickPhotoAsync(options);
// var originalImageStream = file.GetStream();
byte[] resizedImage = await CrossImageResizer.Current.ResizeImageWithAspectRatioAsync(originalImageStream, 500, 1000);
I tried use CrossImageResizer.Current... but I did not find it in the Media Plugin. Instead I found an option called MaxWidthHeight, that worked only if you also add PhotoSize = PhotoSize.MaxWidthHeight option.
For Example :
var file = await CrossMedia.Current.PickPhotoAsync(new PickMediaOptions() { PhotoSize = PhotoSize.MaxWidthHeight, MaxWidthHeight = 600 });
var file = await CrossMedia.Current.TakePhotoAsync(new StoreCameraMediaOptions { PhotoSize = PhotoSize.MaxWidthHeight, MaxWidthHeight = 600 });
Sadly enough there isn't a good cross-platform image resizer (that I've found at the time of this post). Image processing wasn't really designed to take place in a cross-platform environment for iOS and Android. It's much faster and cleaner to perform this on each platform using platform-specific code. You can do this using dependency injection and the DependencyService (or any other service or IOC).
AdamP gives a great response on how to do this Platform Specific Image Resizing
Here is the code taken from the link above.
iOS
public class MediaService : IMediaService
{
public byte[] ResizeImage(byte[] imageData, float width, float height)
{
UIImage originalImage = ImageFromByteArray(imageData);
var originalHeight = originalImage.Size.Height;
var originalWidth = originalImage.Size.Width;
nfloat newHeight = 0;
nfloat newWidth = 0;
if (originalHeight > originalWidth)
{
newHeight = height;
nfloat ratio = originalHeight / height;
newWidth = originalWidth / ratio;
}
else
{
newWidth = width;
nfloat ratio = originalWidth / width;
newHeight = originalHeight / ratio;
}
width = (float)newWidth;
height = (float)newHeight;
UIGraphics.BeginImageContext(new SizeF(width, height));
originalImage.Draw(new RectangleF(0, 0, width, height));
var resizedImage = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
var bytesImagen = resizedImage.AsJPEG().ToArray();
resizedImage.Dispose();
return bytesImagen;
}
}
Android
public class MediaService : IMediaService
{
public byte[] ResizeImage(byte[] imageData, float width, float height)
{
// Load the bitmap
BitmapFactory.Options options = new BitmapFactory.Options();// Create object of bitmapfactory's option method for further option use
options.InPurgeable = true; // inPurgeable is used to free up memory while required
Bitmap originalImage = BitmapFactory.DecodeByteArray(imageData, 0, imageData.Length, options);
float newHeight = 0;
float newWidth = 0;
var originalHeight = originalImage.Height;
var originalWidth = originalImage.Width;
if (originalHeight > originalWidth)
{
newHeight = height;
float ratio = originalHeight / height;
newWidth = originalWidth / ratio;
}
else
{
newWidth = width;
float ratio = originalWidth / width;
newHeight = originalHeight / ratio;
}
Bitmap resizedImage = Bitmap.CreateScaledBitmap(originalImage, (int)newWidth, (int)newHeight, true);
originalImage.Recycle();
using (MemoryStream ms = new MemoryStream())
{
resizedImage.Compress(Bitmap.CompressFormat.Png, 100, ms);
resizedImage.Recycle();
return ms.ToArray();
}
}
WinPhone
public class MediaService : IMediaService
{
private MediaImplementation mi = new MediaImplementation();
public byte[] ResizeImage(byte[] imageData, float width, float height)
{
byte[] resizedData;
using (MemoryStream streamIn = new MemoryStream(imageData))
{
WriteableBitmap bitmap = PictureDecoder.DecodeJpeg(streamIn, (int)width, (int)height);
float Height = 0;
float Width = 0;
float originalHeight = bitmap.PixelHeight;
float originalWidth = bitmap.PixelWidth;
if (originalHeight > originalWidth)
{
Height = height;
float ratio = originalHeight / height;
Width = originalWidth / ratio;
}
else
{
Width = width;
float ratio = originalWidth / width;
Height = originalHeight / ratio;
}
using (MemoryStream streamOut = new MemoryStream())
{
bitmap.SaveJpeg(streamOut, (int)Width, (int)Height, 0, 100);
resizedData = streamOut.ToArray();
}
}
return resizedData;
}
}
EDIT: If you are already using FFImageLoading in your project then you can just use that for your platform.
https://github.com/luberda-molinet/FFImageLoading
I fixed in my project, this was the best way for me .
when take photo or get image from gallery you can change size with properties
var file = await CrossMedia.Current.TakePhotoAsync(new StoreCameraMediaOptions
{
PhotoSize = PhotoSize.Custom,
CustomPhotoSize = 90 //Resize to 90% of original
});
for more information: https://github.com/jamesmontemagno/MediaPlugin
I'm trying to get a rectangle to rotate 10 degrees counterclockwise, in a game loop. I want the box to only rotate 10 degrees. Not add another 10 degrees on the next loop, because that's what it's doing:
First iteration of Game Loop
Second Iteration of Game Loop
Here is my current draw function:
...
class Bok {
static width = 17;
static height = 12;
constructor(position) {
this.position = position;
}
draw() {
...
ctx.beginPath();
ctx.rect(this.position.x, this.position.y, 17, 12);
ctx.fillStyle = "red";
ctx.strokeStyle = "black";
ctx.lineWidth = 10;
ctx.translate(this.position.x + Bok.width / 2, this.position.y + Bok.height / 2);
// this keeps adding 10 degrees to the box every update,
// how can i rotate it 10 degrees without adding on to the
// previous rotation?
ctx.rotate(10);
ctx.translate(-(this.position.x + Bok.width / 2), -(this.position.y + Bok.height / 2));
ctx.stroke();
ctx.fill();
...
}
...
First, looking at your code it seems you got confused on how the API works.
You need to transform the context before you draw (define) the path.
Now, there are many ways to not accumulate transformations:
The long way: perform the inverse transformation.
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const duration = 5000;
const cx = 150;
const cy = 80;
const start = performance.now();
function draw( time ) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const delta = ((start - time) % duration) / duration;
ctx.translate( cx, cy );
ctx.rotate( Math.PI * 2 * delta );
ctx.translate( -cx, -cy );
ctx.strokeRect( cx - 20, cy - 40, 40, 80 );
// inverse
ctx.translate( cx, cy );
ctx.rotate( Math.PI * 2 * delta * -1);
ctx.translate( -cx, -cy );
// an horizontal line
ctx.fillRect(0, 80, canvas.width, 5);
requestAnimationFrame( draw );
}
requestAnimationFrame( draw );
<canvas></canvas>
The short but slow way, save() before drawing and restore() after. But this will save and restore every properties of your canvas context, which may be an overkill.
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const duration = 5000;
const cx = 150;
const cy = 80;
const start = performance.now();
function draw( time ) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const delta = ((start - time) % duration) / duration;
ctx.save();
ctx.translate( cx, cy );
ctx.rotate( Math.PI * 2 * delta );
ctx.translate( -cx, -cy );
ctx.strokeRect( cx - 20, cy - 40, 40, 80 );
ctx.restore();
// an horizontal line
ctx.fillRect(0, 80, canvas.width, 5);
requestAnimationFrame( draw );
}
requestAnimationFrame( draw );
<canvas></canvas>
The clean way: reset your context transform to an identity matrix.
This may look a bit more complicated, but when your code will grow and you'll have a lot objects to manage, having them all relative to the identity matrix will save you from a lot of troubles.
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const duration = 5000;
const cx = 150;
const cy = 80;
const start = performance.now();
function draw( time ) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const delta = ((start - time) % duration) / duration;
ctx.translate( cx, cy );
ctx.rotate( Math.PI * 2 * delta );
ctx.translate( -cx, -cy );
ctx.strokeRect( cx - 20, cy - 40, 40, 80 );
// clear to identity transform
ctx.setTransform(1, 0, 0, 1, 0 ,0);
// an horizontal line
ctx.fillRect(0, 80, canvas.width, 5);
requestAnimationFrame( draw );
}
requestAnimationFrame( draw );
<canvas></canvas>
The simplest solution would be to make a trigger for it.
class Bok {
static width = 17;
static height = 12;
constructor(position) {
this.position = position;
this.rotated = false;
}
draw() {
...
ctx.beginPath();
ctx.rect(this.position.x, this.position.y, 17, 12);
ctx.fillStyle = "red";
ctx.strokeStyle = "black";
ctx.lineWidth = 10;
ctx.translate(this.position.x + Bok.width / 2, this.position.y + Bok.height / 2);
if (!this.rotated) {
ctx.rotate(10);
this.rotated = true;
}
ctx.translate(-(this.position.x + Bok.width / 2), -(this.position.y + Bok.height / 2));
ctx.stroke();
ctx.fill();
...
}
...
adding updated code to rotate based on velocity
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
canvas.width = innerWidth;
canvas.height = 500;
let friction = 0.8;
let gravity = 1.5;
class Player {
constructor() {
this.x = 128;
this.y = 260;
this.w = 32;
this.h = 32;
this.vx = 0;
this.vy = 0;
this.speed = 3;
this.color = "green";
this.a = 0;
this.r = this.a * (Math.PI/180);
}
draw() {
ctx.save();
ctx.fillStyle = this.color;
ctx.translate(this.x+this.w/2, this.y+this.h/2)
ctx.rotate(-this.r);
ctx.fillRect(0, 0, this.w, this.h);
ctx.restore();
}
update() {
if (controller.space) {
this.vy < 10 ? this.vy -= this.speed : this.vy = 10;
this.a < 10 ? this.a += 0.5 : this.a = 10;
} else {
this.a > 0 ? this.a -= 0.5 : this.a = 0;
}
this.r = this.a * (Math.PI/180);
this.vy += gravity;
this.y += this.vy;
this.vy *= friction;
this.draw()
}
canvasCollision() {
if (this.x <= 0) this.x = 0;
if (this.y <= 0) this.y = 0;
if (this.x + this.w >= canvas.width) this.x = canvas.width - this.w;
if (this.y + this.h >= canvas.height) {
this.y = canvas.height - this.h;
this.vy = 0;
}
}
}
let player = new Player();
function handlePlayer() {
player.draw();
player.update();
}
class Controller {
constructor() {
this.space = false;
let keyPress = (e) => {
if (e.code === "Space") this.space = e.type === "keydown";
};
window.addEventListener("keydown", keyPress);
window.addEventListener("keyup", keyPress);
}
}
let controller = new Controller();
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.font = 'bold 48px serif';
ctx.fillText("Spacebar", 100, 50);
player.update();
player.canvasCollision();
requestAnimationFrame(animate);
}
animate();
<canvas id="canvas"></canvas>
I generate the scene above using the OnDraw method below:
protected override void OnDraw(SKCanvas canvas, int width, int height)
{
int i = 0;
int step = 0;
List<SKRect> rects = new List<SKRect>();
// get the 2D equivalent of the 3D matrix
var rotationMatrix = rotationView.Matrix;
// get the properties of the rectangle
var length = Math.Min(width / 6, height / 6);
canvas.Clear(EffectMedia.Colors.XamarinLightBlue);
foreach (var n in numbers)
{
var rect = new SKRect(0 + step, 0, 100 + step, 100);
rects.Add(rect);
step += 120;
}
//var sideHoriz = rotationMatrix.MapPoint(new SKPoint(0, 1)).Y > 0;
var sideVert = rotationMatrix.MapPoint(new SKPoint(1, 0)).X > 0;
var paint = new SKPaint
{
Color = sideVert ? EffectMedia.Colors.XamarinPurple : EffectMedia.Colors.XamarinGreen,
Style = SKPaintStyle.Fill,
IsAntialias = true
};
// first do 2D translation to the center of the screen
canvas.Translate((width - (120 * numbers.Count)) / 2, height / 2);
// The following line is disabled because it makes the whole canvas rotate!
// canvas.Concat(ref rotationMatrix);
foreach (var n in numbers)
{
canvas.RotateDegrees((float)-3);
canvas.DrawRoundRect(rects[i], 30, 30, paint);
var shadow = SKShader.CreateLinearGradient(
new SKPoint(0, 0), new SKPoint(0, length * 2),
new[] { paint.Color.WithAlpha(127), paint.Color.WithAlpha(0) },
null,
SKShaderTileMode.Clamp);
var paintShadow = new SKPaint
{
Shader = shadow,
Style = SKPaintStyle.Fill,
IsAntialias = true,
BlendMode = SKBlendMode.SoftLight
};
foreach (var r in rects)
{
r.Offset(0, 105);
canvas.DrawRoundRect(r, 30, 30, paintShadow);
}
i++;
}
}
The idea is to make all those rounded boxes rotate (vertically) around their own axis.
I tried using SKPath + Transform, saving&restoring the rotationMatrix and/or the canvas but I can't find a way to have 6 rotating boxes ( canvas.Concat(ref rotationMatrix); makes the whole canvas rotate [*]).
Do you have any hint on how that can be achieved?
Note [*]: there's a call to rotationView.RotateYDegrees(5) every X milliseconds to update the rotationMatrix used by OnDraw.
This is what I'd like to achieve, any hints / directions would be really appreciated... :-)
The following piece of code rotates those shapes around their Z-axis:
canvas.Save();
canvas.RotateDegrees(degrees, rects[i].MidX, rects[i].MidY);
canvas.DrawRoundRect(rects[i], 30, 30, paint);
canvas.Restore();
Thanks
There are two issues I'm trying to hammer through still in this canvas script to get this image the way I want it to look. The green is just for demo. Eventually it will be white with a gray drop shadow.
1) I want to add a gray drop shadow to the image after it gets it's green border.
2) I want the corners of the green border to be square, not rounded.
var c = document.getElementById("canvas"),
ctx = c.getContext("2d"),
opaqueAlpha = 255,
img = new Image();
img.onload = function(){
ctx.shadowColor = '#0f0'; // green for demo purposes
ctx.shadowBlur = 20;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.drawImage(img, 30, 30);
img = ctx.getImageData(0, 0, ctx.canvas.width - 1, ctx.canvas.height - 1);
// turn all non-transparent pixels to full opacity
for (var i = img.data.length; i > 0; i -= 4) {
if (img.data[i+3] > 0) {
img.data[i+3] = opaqueAlpha;
}
}
// write transformed opaque pixels back to image
ctx.putImageData(img, 0, 0);
// trying to get the img again and then apply the gray drop shadow...not working
img = ctx.getImageData(0, 0, ctx.canvas.width - 1, ctx.canvas.height - 1);
// need to add a gray shadow to the now opaque border
ctx.shadowColor = '#aaa';
ctx.shadowBlur = 10;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.putImageData(img, 0, 0);
};
img.setAttribute('crossOrigin', '');
img.src = "https://i.ezr.io/racks/bb0e6dd421df72541a79f271fb4f1a90.png?" + new Date().getTime();
<canvas id="canvas" width="800" height="800"></canvas>
This is how I would do it:
I know the width and the height of the image (img.width, img.height)
Also I know the offset: 30. You begin to draw the image at 30 units from the origin.
Since those distinctions are looking more or less the same I need to calculate 3 points to know how to draw the upper notches.
I hope this is what you need:
var c = document.getElementById("canvas"),
cw = (canvas.width = 750),
ch = (canvas.height = 310),
ctx = c.getContext("2d"),
opaqueAlpha = 255,
img = new Image();
img.onload = function() {
let offset = 30;
let whiteBorder = 15;
ctx.drawImage(img, offset, offset);
var imgData = ctx.getImageData(0, 0, cw, ch);
var pixels = imgData.data;
//get some points
let y = offset + 5;
let index = y * imgData.width * 4;
let x1;
for (let i = 0; i < imgData.width * 4; i += 4) {
if (pixels[index + i + 3] > 0) {
//console.log(i / 4);
x1 = i / 4;
break;
}
}
let x2;
for (let i = imgData.width * 4; i > 0; i -= 4) {
if (pixels[index + i] > 0) {
//console.log(i / 4);
x2 = i / 4;
break;
}
}
let x = (offset + 5) * 4;
let y1;
for (let i = 0; i < imgData.height; i++) {
if (pixels[i * imgData.width * 4 + x] > 0) {
//console.log(i);
y1 = i;
break;
}
}
// draw the border behind the image
ctx.globalCompositeOperation='destination-over';
ctx.beginPath();
ctx.moveTo(offset, img.height + offset);
ctx.lineTo(img.width + offset, img.height + offset);
ctx.lineTo(img.width + offset, y1);
ctx.lineTo(x2, y1);
ctx.lineTo(x2, offset);
ctx.lineTo(x1, offset);
ctx.lineTo(x1, y1);
ctx.lineTo(offset, y1);
ctx.closePath();
ctx.strokeStyle = "white";
ctx.lineWidth = 25;
ctx.stroke();
};
img.setAttribute("crossOrigin", "");
img.src =
"https://i.ezr.io/racks/bb0e6dd421df72541a79f271fb4f1a90.png?" +
new Date().getTime();
canvas{
filter:drop-shadow(0px 0px 5px #333);
}
<canvas id="canvas"></canvas>
I did not find a direct function in Color section or since there is no direct C# Unity function for picking the color from post render. How is the best approach for picking the color in the mouse position?
I have done research and looks like there is posible to make a screenshot and then look into the texture calculating the mouse position.
Input.GetMouseButtonDown(0)
Application.CaptureScreenshot("Screenshot.png");
// get the color pixel in the same coordinates of the mouse position
Vector3 mouseCoordinates = Input.mousePosition;
myFinalColor = tex.GetPixel((int)mouseCoordinates.x, (int)mouseCoordinates.y);
Or do I have to make a second camera and attach it to a mesh render?
You just need to use GetPixel(x,y)
This is very simple.
Save Screenshot to Texture2D for example MyTexture
And add .GetPixel( x postion of moue , Y position of mouse )
Save it to your color for GetScreenShot ( make your View to Texture2D )
Color TheColorPicked;
if (Input.GetMouseButtonDown(0))
{
TheColorPicked = MyTexture.GetPixel(Input.mousePosition.x,
Input.mousePosition.y);
}
Yes you can make your own colour picker. Here is the code thanks to Git-hub this page.
using UnityEngine;
using System.Collections;
// relies on: http://forum.unity3d.com/threads/12031-create-random-colors?p=84625&viewfull=1#post84625
public class ColorPicker : MonoBehaviour {
public bool useDefinedPosition = false;
public int positionLeft = 0;
public int positionTop = 0;
// the solid texture which everything is compared against
public Texture2D colorPicker;
// the picker being displayed
private Texture2D displayPicker;
// the color that has been chosen
public Color setColor;
private Color lastSetColor;
public bool useDefinedSize = false;
public int textureWidth = 360;
public int textureHeight = 120;
private float saturationSlider = 0.0F;
private Texture2D saturationTexture;
private Texture2D styleTexture;
public bool showPicker = false;
void Awake() {
if (!useDefinedPosition) {
positionLeft = (Screen.width / 2) - (textureWidth / 2);
positionTop = (Screen.height / 2) - (textureHeight / 2);
}
// if a default color picker texture hasn't been assigned, make one dynamically
if (!colorPicker) {
colorPicker = new Texture2D(textureWidth, textureHeight, TextureFormat.ARGB32, false);
ColorHSV hsvColor;
for (int i = 0; i < textureWidth; i++) {
for (int j = 0; j < textureHeight; j++) {
hsvColor = new ColorHSV((float)i, (1.0f / j) * textureHeight, 1.0f);
colorPicker.SetPixel(i, j, hsvColor.ToColor());
}
}
}
colorPicker.Apply();
displayPicker = colorPicker;
if (!useDefinedSize) {
textureWidth = colorPicker.width;
textureHeight = colorPicker.height;
}
float v = 0.0F;
float diff = 1.0f / textureHeight;
saturationTexture = new Texture2D(20, textureHeight);
for (int i = 0; i < saturationTexture.width; i++) {
for (int j = 0; j < saturationTexture.height; j++) {
saturationTexture.SetPixel(i, j, new Color(v, v, v));
v += diff;
}
v = 0.0F;
}
saturationTexture.Apply();
// small color picker box texture
styleTexture = new Texture2D(1, 1);
styleTexture.SetPixel(0, 0, setColor);
}
void OnGUI(){
if (!showPicker) return;
GUI.Box(new Rect(positionLeft - 3, positionTop - 3, textureWidth + 60, textureHeight + 60), "");
if (GUI.RepeatButton(new Rect(positionLeft, positionTop, textureWidth, textureHeight), displayPicker)) {
int a = (int)Input.mousePosition.x;
int b = Screen.height - (int)Input.mousePosition.y;
setColor = displayPicker.GetPixel(a - positionLeft, -(b - positionTop));
lastSetColor = setColor;
}
saturationSlider = GUI.VerticalSlider(new Rect(positionLeft + textureWidth + 3, positionTop, 10, textureHeight), saturationSlider, 1, -1);
setColor = lastSetColor + new Color(saturationSlider, saturationSlider, saturationSlider);
GUI.Box(new Rect(positionLeft + textureWidth + 20, positionTop, 20, textureHeight), saturationTexture);
if (GUI.Button(new Rect(positionLeft + textureWidth - 60, positionTop + textureHeight + 10, 60, 25), "Apply")) {
setColor = styleTexture.GetPixel(0, 0);
// hide picker
showPicker = false;
}
// color display
GUIStyle style = new GUIStyle();
styleTexture.SetPixel(0, 0, setColor);
styleTexture.Apply();
style.normal.background = styleTexture;
GUI.Box(new Rect(positionLeft + textureWidth + 10, positionTop + textureHeight + 10, 30, 30), new GUIContent(""), style);
}
}
You can also find this helpfull asset store packages 1 2.