Resize proportionally and crop image in any aspect like top right,top left..... on html5canvas [duplicate] - html5-canvas

I'm trying to upload images and have them fit into different sized boxes....To give you an idea of what the application does: People upload images and have them printed onto posters.
For example, we'd have a poster size of 8" x 10"(live area) and the full size is 9.5" x 11.5", since the minimum DPI is 100, we typically multiple the 8x10 by 100 = 800x1000.
Here's an image explaining that I have an original image(http://i.imgur.com/Uds9rcZ.jpg) and need it to fit accordingly to the different sizes.
I may need to clarify this a bit, so ask questions if needed.
Thanks.

Canvas's context.drawImage has a version which allows you to scale an image while you are drawing it to the canvas.
If you resize disproportionally (like you do in your example) some of your resized image will fall off the canvas. Then your kitty will look distorted (in your example: stretched vertically)
This sample code resizes proportionally by using only the width. This way your kitty is not stretched.
// calculate how much to scale the resulting image
var originalWidth=16;
var originalHeight=20;
var desiredWidth=20;
var scalingFactor = desiredWidth/originalWidth;
// scale the original size proportionally
var newWidth=originalWidth*scalingFactor;
var newHeight=originalHeight*scalingFactor;
// resize the canvas to fit the desired image size
// Note: canvas is a reference to your html canvas element
canvas.width=newWidth;
canvas.height=newHeight;
// Draw the image to the canvas
// This version of drawImage allows you to scale the original image
// while you are drawing it to the canvas.
context.drawImage(
originalImage,
0,0,originalWidth,originalHeight,
0,0,newWidth,newHeight);

I would like to suggest you use the easyCanvas library to do this. The reason for this is that the built-in drawImage method of context do not scale the image proportionally for you, and it involves a small dose of math to get it right, especially in cases where destination shape differs from original and you want it to cover the whole area.
I made a method in this library to handle situations such as these allowing you to draw the original image proportionally into any shape even if the shape doesn't correspond with the original.
See this demo for an live example.
In essence what you do is to draw your uploaded image into the canvas with this method:
ez.drawImageProp(image, x, y, width, height);
where width and height would be that of destination.
It also has offset parameters so you can move the image around within that shape where an offset of 0.5 is center, 0 is from left and 1 is from right:
ez.drawImageProp(image, x, y, width, height, offsetX, offsetY);
Assuming image is already available all you need to do is:
var ez = new easyCanvas('canvas'); /// provide ID of existing canvas
ez.drawImageProp(image, 0, 0, ez.width, ez.height);
Disclaimer: I am the author of easyCanvas. easyCanvas is open-source (GPL3.0 license).

Related

SkiaSharp Text Size on Xamarin Forms

How does the TextSize property on an SKPaint object relate to the 'standard' Xamarin Forms FontSize?
In the image you can see the difference between size 40 on a label and as painted. What would I need to do to make them the same size?
As #hankide mentioned, it has to do with the fact that the native OS has scaling for UI elements so the app "looks the same size" on different devices.
This is great for buttons and all that as the OS is drawing them. So if the button is bigger, the OS just scales up the text. However, with SkiaSharp, we have no idea what you are drawing so we can't do any scaling. If we were to scale, the image would become blurry or pixelated on the high resolution screens.
One way to get everything the same size is to do a global scale before drawing anything:
var scale = canvasWidth / viewWidth;
canvas.Scale(scale);
And this is often good enough, but sometimes you really want to draw items differently on a high resolution screen. An example would be a tiled background. Instead of stretching the image on a bigger canvas, you may want to just tile it - preserving the pixels.
In the case of this question, you can either scale the entire canvas before drawing, or you can just scale the text:
var paint = new SKPaint {
TextSize = 40 * scale
};
This way, the text size is increased, but the rest of the drawing is on a larger canvas.
I have an example on GitHub: https://github.com/mattleibow/SkiaSharpXamarinFormsDemo
This compares Xamarin.Forms, SkiaSharp and Native labels. (They should all be exactly the same size)
I think that the problem is in the way Xamarin.Forms handles font sizes. For example on Android, you could define the font size in pixels (px), scale-independent pixels (sp), inches (in), millimeters and density-independent pixels (dp/dip).
I can't remember how Xamarin.Forms handles the sizes (px,sp or dp) but the difference you see here is because of that. What you could do, is create an Effect that changes the font size handling on the native control and try to match the sizing provided by SkiaSharp.

Why does canvas re-render the entire canvas rather than only a specific portion?

From my very limited html5 canvas experience, it seems like the way to animate something is to clear the entire canvas, and draw everything from scratch.
This doesn't seem very performant. I'm wondering why this was the chosen approach?
Is there an alternative? For example, using an object-oriented approach, if you wanted to re-render a tree in the foreground, the system should cache the background, and only rerender that layer.
Your understanding is correct.
Typical canvas apps will completely erase the canvas and redraw objects.
This process works well because Html Canvas is designed with blazingly fast drawing speed.
Unlike object oriented design, the data that draws on the canvas has been completely "flattened".
There is a single data array containing the Red, Green, Blue & Alpha components of all pixels on the canvas.
[
pixel1Red, pixel1Green, pixel1Blue, pixel1Alpha,
pixel2Red, pixel2Green, pixel2Blue, pixel2Alpha,
pixel3Red, pixel3Green, pixel3Blue, pixel3Alpha,
...
]
This means that any color component of any pixel can be accessed with a single jump.
This flat structure also means that if an image needs to be drawn on the canvas, the browser only needs to copy sequential data from the source image directly into sequential data in the canvas pixel array.
In addition, Canvas is hardware accelerated when a GPU is available.
That's the basic technique, yes. You could also clear a specific area of the canvas instead, using clearRect():
https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Canvas_tutorial/Drawing_shapes
// Clear the specified rectangular area from x, y through width, height.
context.clearRect(x, y, width, height);
In your case of change foregrounds and backgrounds, however, consider modifying the globalCompositeOperation, which enables you to draw shapes under existing shapes:
// Draw new shapes behind the existing canvas content.
ctx.globalCompositeOperation = 'destination-over';
https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Canvas_tutorial/Compositing
Another useful method is clip(), which allows you to draw shapes as masks:
// Create a circular clipping path.
ctx.beginPath();
ctx.arc(0, 0, 60, 0, Math.PI * 2, true);
ctx.clip();
https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Canvas_tutorial/Compositing

how to calculate the right resolution for an image overlay in openlayers

I have an image I use as overlay in a vector layer in my openlayer web application. The idea is the same as showed in this example
What I do not understand is how to set a valid resolution to my image so that every time the user changes the zoom of the map, the image width and height is adjusted to cover its real geographical area.
In the example above some images are used and placed at some specific locations using a specific and hard-coded resolution factor.
Assume I have an image 400x400 pixel which represents an area of 400x400 kilometers, I need to recalculate the width and height of my image every time the zoom factor changes using a formula like this:
imageWidth = imageWidthInPixel * theResolution / map.getResolution();
imageHeight = imageHeightInPixel * theResolution / map.getResolution();
where 'theResolution' is the value I need to calculate some way I don't know. I guess this is a value that depends on the area expressed in kilometers or meters the image covers, but I am not able to find out a relation that has a sense. As explained above, in the example I reported, these values are hard-coded and depends on the image but there is no way to understand how these values are calculated.
Please help me understand this.
If you know the projection of your image, it would be easiest to simply define the image as a layer defined in OpenLayers with the appropriate projection. At that point, OpenLayers will handle the zooming and panning for you.

Image percentWidth 100 height stays original image height (margin)

What happens with the code below is that image width is scaled to 100% as expected and the height also scales as expected keeping the aspect ratio correct. Issue is that there is a margin at the bottom and that seems to be the height of the original contentHeight of the image. How can I get rid of that?
I am using percentages so that it scales when device orientation changes.
backdrop.source = "http://cf2.imgobject.com/t/p/" + "w342" + data.backdrop;
backdrop.scaleMode = "letterbox";
backdrop.horizontalAlign = "left";
backdrop.verticalAlign = "top";
backdrop.smooth = true;
backdrop.percentWidth = 100;
The answer to your question is don't use the letterbox setting. That is going to preserve the aspect ratio and make the black area, hence the name letterbox :)
Try setting scaleMode to zoom instead. As the documentation states, zoom will result in one axis being clipped. This should scale the image, preserve the aspect ratio, but clip some edges of the image to avoid having the black area.
Other solutions to this problem are:
modify the original image outside of Flash
use a mask to achieve similar results that the zoom setting will provide. In this approach you make the image bigger, but then apply a square mask to the image. The mask reveals only the square portion ... clipping what is outside the mask.
(undesirable in most cases) use the scaleMode setting of strectch (and specify both width/height) so that the area is filled, this will not preserve the aspect ratio
PS: There is no way to avoid the black area if the image's aspect ratio is not square. Even with HTML/CSS. This is just math/geometry. The same thing happens in HTML -- the image is either stretched, clipped, or will not fill both dimensions.
[Edit]
PPS: One other idea, if you know the original aspect ratio of the image, is to calculate a new width that will be closest to the desired width, but naturally preserves the width to height aspect ratio.
For example, the width:height ratio is 4:3. Your desired width is 500 pixels. Using cross products you get this:
4 500
- = -
3 x
Using cross products you get the equation:
4x = 3*500
Now solve for x:
x = 3*500/4 = 375
Therefore, if the original aspect ratio is 4:3, you can set a width of 500 and a height of 375 to scale the image and not have any black areas. You can even write code that dynamically calculates the aspect ratio, and applies this logic to scale something nicely. The point is that you have to the respect aspect ratio when scaling the image to avoid the "black" areas.

Effective image cropping in JavaFX

I'm trying to write a Jigsaw puzzle using JavaFX partly because someone asked me and partly because I want to give JavaFX a go. However, I am having difficulty with the actual cropping of the image.
The idea is for the user to supply the image and the program to cut the image into smaller pieces. Simple, right? My problem is this: The only way I could find to cut the image is to make a copy of the image object and change the visible part of the copy, here's an example:
ImageView imgageView = new ImageView(); // Creates a new ImageView; this will be a single puzzle piece.
imgageView.setImage(originalImage); // Use the original image as the "base."
Rectangle2D rectangle = new Rectangle2D(0, 0, 50, 50); // Crop the image from (0,0) to (50, 50).
Just to clarify the last line, here's the related piece in the API:
public Rectangle2D(double minX,
double minY,
double width,
double height)
Creates a new instance of Rectangle2D.
Parameters:
minX - The x coordinate of the upper-left corner of the Rectangle2D
minY - The y coordinate of the upper-left corner of the Rectangle2D
width - The width of the Rectangle2D
height - The height of the Rectangle2D
Now this is fine if I'm cutting the picture into four or nine pieces (the game is meant for young children), but what if I want to cut the picture into a nice 1200 piece puzzle? Will this not result in an extremely expensive operation? Not only the cropping itself, but to keep that many copies of the image in memory. See, if I'm understanding this correctly each piece will consist of the entire original image with a large portion of it that will remain "hidden."
Am I simply misunderstanding the function? If not, there must be a better way to do this, right?
Using PixelReader and WritableImage should help.
the following cuts a new image from an old one at position (x,y) and size (width, height)
PixelReader reader = oldImage.getPixelReader();
WritableImage newImage = new WritableImage(reader, x, y, width, height);
Multiple ImageView objects can reference the same Image. The data for the image itself is stored in the Image. If you have 1000 ImageView objects each referencing the same Image then there is only 1 copy of the pixels in memory. Creating copies using WriteableImage would actually be more expensive than having multiple ImageView objects.

Resources