Give 2d image customization to produce real view - html5-canvas

Is it possible to add image/text on the 2d image so that it gives a real view.For example as present in : http://www.zazzle.com/make_your_own_iphone_5_case-179092402149274498.

These views are orthographic and isometric views and they can be reproduced using affine transformations in canvas, as they are also parallelograms.
First you will need to make masks for the different cases. These needs to be drawn in the same orientation as the case in the "photo". Use solid pixels (any color will do, it won't show in later step) where you want the custom graphics to show, transparent pixels anywhere else (anti-aliased pixels are fine).
Then draw in the mask in the canvas, select composite mode "source-in" to replace non-transparent pixels and finally, select blending mode "multiply" and draw the case "photo" on top to mix in shadows and highlights. The latter step is what will give the illusion of the image having depth.
For the isometric views, calculate the skew angle (or use trial and error if you're not sure if the image is accurate - this is as a rule-of-thumb usually tan(60°), ie. transform(1, 0, Math.tan(60/180*Math.PI), 1, 0, 0)), then do the same process as above. Just remember only apply transformation when drawing the custom image, mask and top layer must be drawn without transformations.
The orthographic side views can be generated using scaling for the x-axis. Depending on which angle, add a stripe for the side of the case.
Example of steps
var img = new Image(),
cust = new Image(),
count = 2,
ctx = document.querySelector("canvas").getContext("2d");
img.onload = cust.onload = comp;
img.src = "http://i.stack.imgur.com/je0Jh.png";
cust.src = "http://i.stack.imgur.com/uRPDt.png";
function comp() {
if (--count) return;
// draw in mask
ctx.drawImage(img, 0, 0);
// comp. mode source-in
ctx.globalCompositeOperation = "source-in";
// draw in custom graphics
ctx.drawImage(cust, 0, 0, ctx.canvas.width, ctx.canvas.height);
// blend mode multiply
ctx.globalCompositeOperation = "multiply";
// draw in original case multiplied (does not work in IE)
ctx.drawImage(img, 0, 0);
}
<canvas with=263 height=505></canvas>
The quality largely depends on the quality of the mask - I made a very quick-n-dirty version here as you can see (your case image can also act as the mask btw).
The steps are the same for the isometric view with the exception of the skew transform. Multiply does not work in IE, you can use alpha here instead or make a separate mask containing only shadows etc.
That being said: remember that this is not the image sent to production. This will just show a representation of the final result. What is used is the image, image position and size. These data is then used to build an unmasked flat print-template which is used to make the phone-case.

Related

Draw polygon extending beyond image-border

Note:
The question is specific to PHP GD library only
This question is NOT about how to crop image to a target aspect ratio, rather it is about how to draw overlay extending outside the image
I want to create custom graphic by putting together a background image with a polygon and some texts.
Input background images are of varied dimensions and aspect-ratios, but the final graphic has to be of a fixed (2:1) aspect ratio (it also has to be of pre-defined dimensions, but resizing of image is trivial, so correct aspect ratio is my only target).
Presently I'm cropping-to-fit my input image to target aspect ratio (2:1) by performing max-center area cropping using imagecrop function. Thereafter I draw red polygon on it as shown below (ignore the texts drawn on red band) using imagefilledpolygon method [cropping screenshot below is for demonstration purpose only, it is actually being done programmatically via imagecrop function]
Here's my function that draws the overlay (this function is called after cropping of image to 2:1 aspect ratio is done)
/**
* adds overlay (colored band) on the image
* for better output, overlay must be added before resizing
*/
public function withOverlay(): NotifAdsCreativeGenerator {
// Prepare custom red-color Hex to RGB https://stackoverflow.com/a/15202130/3679900
list($r, $g, $b) = sscanf(self::OVERLAY_COLOR, "#%02x%02x%02x");
$custom_red_color = imagecolorallocate($this->getCrrImage(), $r, $g, $b);
// prepare coordinates for polygon
$coords = [
[0, 0],
[(int) ($this->getCrrWidth() * self::OVERLAY_BEGIN_X_RATIO), 0],
[(int) ($this->getCrrWidth() * self::OVERLAY_END_X_RATIO), $this->getCrrHeight()],
[0, $this->getCrrHeight()]
];
$flattened_coords = array_merge(...$coords);
// draw polygon on image
imagefilledpolygon($this->getCrrImage(), $flattened_coords, count($flattened_coords) / 2, $custom_red_color);
return $this;
}
But what I want is to crop the image to ~ 1.28:1 aspect ratio (the approx ratio of right part of graphic without the red band) and then draw the polygon (extending) outside the image so as to obtain the final graphic in the same same 2:1 aspect ratio as shown below
I'm able to crop image to my desired aspect ratio (1.28:1) but I can't figure out a way to draw the polygon outside the image bounds (effectively expanding the image in the process). Is there a way to do this using PHP-GD library?
It was just a lack of understanding (about working of PHP-GD, available methods) on my part, but the solution is pretty simple
create an empty 'canvas' image of desired dimensions (and the 2:1 target aspect ratio) using imagecreatetruecolor function
(after cropping), copy the image on right side of canvas using imagecopy method (some basic maths has to be done to determine the offset where the image has to be placed on canvas)
now as before, the red polygon can be drawn on the left side on canvas to obtain the final graphic
/**
* adds overlay (colored band) on the image
* for better output, overlay must be added before resizing
*
* This method tries to preserve maximum center region of input image by performing minCenterCrop(.) on it
* before drawing an overlay that extends beyond left border of the cropped image
*
* (since this method incorporates call to 'withMinCenterCrop', calling that method before this is not required
* (and is redundant). For benefits of this method over 'withOverlay', read docstring comment of
* 'withMinCenterCrop' method
* #return NotifAdsCreativeGenerator
*/
public function withExtendedOverlay(): NotifAdsCreativeGenerator {
// perform min center crop to the 1.28:1 aspect ratio (preserve max central portion of image)
$this->withMinCenterCrop();
// this $required_canvas_aspect_ratio calculates to 2.0 (2:1 aspect ratio)
// calculate aspect ratio & dimensions of empty 'canvas' image
// since canvas is wider than min center-cropped image (as space on the left will be occupied by red overlay)
// therefore it's height is matched with cropped image and width is calculated
$required_canvas_aspect_ratio = self::IMAGE_WIDTH / self::IMAGE_HEIGHT;
// height of cropped image
$canvas_height = $this->getCrrHeight();
$canvas_width = $required_canvas_aspect_ratio * $canvas_height;
// create a new 'canvas' (empty image) on which we will
// 1. draw the existing input 'min-cropped' image on the right
// 2. draw the red overlay on the left
$canvas_image = imagecreatetruecolor($canvas_width, $canvas_height);
// copy contents of image on right side of canvas
imagecopy(
$canvas_image,
// cropped image
$this->getCrrImage(),
self::OVERLAY_BEGIN_X_RATIO * $canvas_width,
0,
0,
0,
// dimensions of cropped image
$this->getCrrWidth(),
$this->getCrrHeight()
);
// draw red band overlay on left side of canvas
$this->crr_image = $canvas_image;
return $this->withOverlay();
}

How to detect two vertical lines with image processing?

I am trying to detect two vertical lines shown in the attached images using some image processing methods. The line are in low contrast.
The location is shown in the first image with yellow arrows.
The original image is also attached.
I tried using adaptiveThresholding and normal thresholding using maximum and minimum at local windows. But I can't detect the lines.
Any ideas how to detect the two vertical lines in image processing?
There is some trick when contrast is low in bright pixels. There is thresholding method - otsu thresholding (https://en.wikipedia.org/wiki/Otsu%27s_method), which can be used to detect bright side of histogram. After that, you can normalize that part of histogram to (0,255) and set 0 to darker pixels as in code below:
cv::Mat img = cv::imread("E:\\Workspace\\KS\\excercise\\sjB8q.jpg", 0);
cv::Mat work;
for (int i = 0; i < 4; i++) // number of iterations has to be adjusted
{
cv::threshold(img, work, 30, 255, CV_THRESH_OTSU);
cv::bitwise_and(img, work,img);
cv::normalize(img, img, 0, 255, cv::NORM_MINMAX, -1, work);
}
Then your contrast will be better like in pictures below (for different iterations):
i=2:
i=4:
i =6:
After that preprocessing detecting dark lines should be easier. That answer is just explanation of idea. If you want to know more just ask in comment.

Getting rid of artifacts on generated gradient textures

I'm generating a falloff texture by adding gradient part to the white image I have. If implementation is relevant, I'm doing it with HTML5 canvas. For some reason I'm getting weird ray like while artifacts where it's supposed to be gradient smooth. I couldn't find any way to take care of that on implementation level, so I have to get rid of them after generating. Question is, if I have per pixel access to the image, how do I recognize those white pixels and replace with pixels to keep the gradient smooth?
The rays are caused by overlaps and rounding errors. They can be removed or at least reduced by using a Gaussian blur filter (which in effect act as a low-pass filter).
To avoid new problems such as the inner shape's black pixels leaking into the gradient, I'd suggest these steps:
Fill inner shape in the same color as the start color of the gradient.
Produce gradients
Apply Gaussian blur using either the filter property of context (f.ex context.filter = "blur(7px)";, reset by setting it to none), or by using a manual implementation
Redraw the inner shape in the destination color.
Now it's a simple matter of experimenting with the blur radius to find an optimal value. Note that blurring will add to the gradient so you might want to link the two so that the radius of the gradient is reduced when blur radius is increased.
Pro-tip: you can also drop the gradient production all together and simply make the glow effect using Gaussian blur (run example below).
var ctx = c.getContext("2d");
ctx.moveTo(300, 50);
ctx.quadraticCurveTo(325, 300, 550, 550);
ctx.quadraticCurveTo(300, 500, 50, 550);
ctx.quadraticCurveTo(250, 300, 300, 50);
ctx.closePath();
// blur next drawings
ctx.filter = "blur(20px)"; // glow radius
// produce a full base using fill and heavy stroke
ctx.fillStyle = ctx.strokeStyle = "#fff";
ctx.fill();
ctx.lineWidth = 40; // thicker = stronger spread
ctx.stroke();
// final, fill center in destination color
ctx.filter = "none";
ctx.fillStyle = "#000";
ctx.fill();
#c {background:#000}
<canvas id=c width=600 height=600></canvas>

What code should I use with 'drawMatrix.setPolyToPoly' to manipulate and draw just a rectangular part of a bitmap, rather than the entire bitmap?

When i want to use just part of a bitmap without skewing or rotating it, like the top left fourth, the following works well:
src_rect.set(0, 0, bmp_test.getWidth()/2,bmp_test.getHeight()/2);
canvas.drawBitmap(src_rect, dst_rectF, paint);
This draws just one fourth of the bitmap.
However, if i want to rotate or skew just part of a bitmap using a matrix, the following does not work as expected:
srcpairs[0] = 0;
srcpairs[1] = 0;
srcpairs[2] = (float)bmp_test.getWidth()/2;
srcpairs[3] = 0;
srcpairs[4] = (float)bmp_test.getWidth()/2;
srcpairs[5] = (float)bmp_test.getHeight()/2;
srcpairs[6] = 0;
srcpairs[7] = (float)bmp_test.getHeight()/2;
drawMatrix.setPolyToPoly(srcpairs, 0, dstpairs, 0, 4);
canvas.drawBitmap(bmp_test, drawMatrix, paint);
This does not just draw the top left forth of the bitmap, like
canvas.drawBitmap(src_rect, dst_rectF, paint)
does. Instead it draws the entire bitmap, 4 times bigger than the dstpairs area, aligning the top left fourth of the bitmap (the srcpairs points) to the destpairs points.
What are the simplest changes to be made to this matrix code to manipulate and draw just a rectangular part of a bitmap?
I want to avoid creating separate bitmaps of parts of the original image. That is what I'm having to do now.

Rotating a single image (but not the others) on an html5 canvas?

I have a sprite I'm animating on an html canvas using normal sprite sheet blitting. On certain key events I'd like to change the direction of the sprite (ie, flip it, or rotate it 180 degrees) without changing anything (the other sprites) on the canvas.
Does anyone know how to do this?
So I was having this issue with my game; I had cells for up, down, and left animations, but no right cells. So I needed to flip the left cells to draw the right cells.
For each sprite I keep track of it's current top and left in the canvas, as well as each cell's top and left in the sprite sheet.
I've seen previous answers showing a simple horizontal flip as just translating the origin and flipping (inverse scale) of the axes, BUT this does not take into account that with sprites, flipping the origin will mess up the sprite's registration point (its top and left on the canvas).
This issue manifested in the sprite being mirrored correctly, but it's position being off by the width of the sprite. I solved it by taking into account the width of the sprite. Notice how I'm using CanvasRenderingContext2D.prototype.drawImage with 9 arguments since I'm slicing a sprite out of a sprite sheet:
// check if we need to flip image (special case for right movement)
if(sprite.translated){
context.save();
context.translate(context.canvas.width, 0);
context.scale(-1, 1);
context.drawImage(sprite.sheet,
cell.left,
cell.top,
sprite.width,
sprite.height,
// secret sauce: change the destination's X registration point
context.canvas.width - sprite.left - sprite.width,
sprite.top,
sprite.width, sprite.height);
context.restore();
} else {
// Assumes cells are all the same width and height, set in sprite
context.drawImage(sprite.sheet, cell.left, cell.top, sprite.width,
sprite.height, sprite.left, sprite.top, sprite.width, sprite.height);
}
Note: I also could have done the math in the translate, since it's meant to simplify calculations elsewhere.
Simply redraw the sprite, with a rotate transformation. Transformations in HTML Canvas 2D Context
The canvas is just an off-screen buffer. It won't be cleared unless you tell it to, and nothing else will be changed unless you tell it to.
There's a bunch of different situations in which you may have to redraw the area of or around the sprite. Otherwise, you'll get a ghosting effect where part of the old sprite is still visible below the new drawing, or other drawings become obscured. Some reasons are:
Your sprite is partially transparent,
Your sprite is partially translucent,
Other drawings are made on top of your sprite,
Your sprite is non-rectangular,
You're doing flips that are not multiples of 90 degrees.
So that might be a bit more work, and there are several different approaches to doing that. You could simply redraw the entire scene, or just the specific objects at the location, perhaps using the clip method.
A completely different direction might be to use other HTML elements, img or div, with absolute positioning and CSS3 transformations. That's basically a bit of trickery to delegate the rendering of your scene to the browser.
While I appreciate Shtééf's answer, after a bit of research, I have found that rotating the canvas you are actually using to display doesn't seem to be ideal. The saving, rotating and restoring while trying to create complex animations (aka think Street Fighter 2 not astroids) causes the canvas to flicker in even Chrome.
I have found however a usable strategy. The idea here is that you actually create two canvases, one will be for your game and the other will be a backbuffer of sorts and it will be used to rotate or scale your sprites. You essentially transform the backbuffer canvas, draw the image in question, then transfer it to your main canvas and restore (or not) the backbuffer. In this manner, you only rotate the hidden canvas and only effect the sprite in question not the entire game board.
The code looks something like this (work in progress):
mainContext.clearRect(lastXpos, lastYpos, lastWidth, lastHeight);
backContext.clearRect(0, 0, lastWidth, lastHeight);
lastXpos = xpos;
lastYpos = ypos;
lastWidth = width;
lastHeight = height;
backContext.save();
//check the direction of the sprite
//turn the backContext to this direction
//SPRITE_INVERTED==-1
if (spriteXDirection == SPRITE_INVERTED || spriteYDirection == SPRITE_INVERTED)
{
var horScale = 0;
var verScale = 0;
if (spriteXDirection == SPRITE_INVERTED)
{
horScale = width;
}
if (spriteYDirection == SPRITE_INVERTED)
{
verScale = height;
}
backContext.translate(horScale, verScale);
backContext.scale(spriteXDirection, spriteYDirection);
}
//draw the sprite not we always use 0,0 for top/left
backContext.drawImage(animations[currentPlay].sheet,
animationX,
animationY,
width,
height, 0, 0, width, height);
//Get the image data from the back context
var image = backContext.getImageData(0, 0, width, height);
//flip the back context back to center - or not, I haven't decided how to optimize this yet.
backContext.restore();
//transfer the image to your main context
mainContext.putImageData(image, xpos, ypos);
This has saved me a lot of headaches in understanding how to translate my sprites without having everything on my gameboard move all over the place. It also seems to perform better then modifying the main context.
Why don't you use save() and restore
ctx.save(); // save current state
ctx.rotate(Math.PI); // rotate
ctx.drawImage(link,x,y,20,20); // draws a chain link or dagger
ctx.restore(); // restore original states (no rotation etc)
How to rotate one image in a canvas?

Resources