It is possible that I can change the image color of png image by php/js functions?
I would like to change color only on non-transparent area. An example image is available here:
I would like to change colors only on the visible t-shirt not on all area.
Please post your actual transparent PNG input file. In lieu of that, in ImageMagick command line, this I think should work to colorize the white to sky-blue.
convert image.png \
\( -clone 0 -alpha off -fill skyblue -colorize 100 \) \
\( -clone 0 -alpha extract \) \
-compose multiply -composite \
result.png
I do not know PHP Imagick that well. So I will leave my answer to someone to translate to Imagick.
ImageMagick could be a good route to take (check out PHP.net's Imagick reference, and phpimagick.com), as it'll do the operation on the server, and 'theoretically' work no matter what, (e.g., even if the client's JS is turned off).
If you're not able to use it (e.g., your hosting's limited), an option via JavaScript is to use an offscreen canvas, draw the images to it, generate a PNG from it, and then finally add it to the page.
Here's an example of some working code:
// shirtPath: the url to the shirt
// overlayPath: the url to the overlay image
// callback: a callback that is given the image once it's generated
function generateShirtOverlay(shirtPath,overlayPath,callback) {
// load the images
var loadedImages = 0;
function imageLoaded() {
loadedImages++;
if(loadedImages == 2) {
composeImages();
}
}
var shirtImage = new Image(),
overlayImage = new Image();
// set the image onload callbacks
shirtImage.onload = imageLoaded;
overlayImage.onload = imageLoaded;
// load the images
shirtImage.src = shirtPath;
overlayImage.src = overlayPath;
var generatedImage = new Image();
function composeImages() {
// get image sizes
var width = shirtImage.width,
height = shirtImage.height,
overlayWidth = overlayImage.width,
overlayHeight = overlayImage.height;
// create a canvas
var canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
// draw the shirt to it
var context = canvas.getContext("2d");
context.drawImage(shirtImage,0,0);
// set the global composite operator to multiply
// this overlays the image nicely, keeping the
// dark areas on the shirt dark
context.globalCompositeOperation = "multiply";
// draw the overlay image, centering it if it's
// not the same size
context.drawImage(overlayImage,
(width - overlayWidth) / 2,
(height - overlayHeight) / 2
);
// a bit of masking magic! for details, see:
// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation
context.globalCompositeOperation = "destination-in";
// draw the shirt again; this clips the overlay image
// at the shirt's edges
context.drawImage(shirtImage,0,0);
// finally, extract a PNG from the canvas
var png = canvas.toDataURL("image/png");
generatedImage.onload = returnComposedImage();
// and load it to an image object
generatedImage.src = png;
}
function returnComposedImage() {
if(typeof callback == "function")
callback(generatedImage);
}
}
This function could be called to generate the image. Ultimately, it'll call a callback, providing the generated image. Then, since it's an Image object, you can add it right to the page, e.g.:
function addImageToPage(image) {
document.body.appendChild(image);
}
generateShirtOverlay("images/shirt.png", "images/design1.png", addImageToPage);
Please note, though, the disadvantage of client-side rendering is that not all browsers support all these canvas features. In addition, it's a little more overhead on the client-side. Fortunately, though, as it's not an extremely complex manipulation, this method uses only image blend modes, and doesn't need to iterate pixels.
You can use the PHP imagick library. I think it's the best way.
Imagick class
For example :
<?php
$imageInPath = __DIR__. '\\image-before.png';
$imageOutPath = __DIR__. '\\image-after.png';
$overlayColor = '#F00'; // Red for example
$fuzz = 0;
$overlay = new Imagick( $imageInPath ); // Create imagick object
$overlay->opaquePaintImage ( $overlay->getImagePixelColor(0, 0) , $overlayColor , $fuzz , true ); // Fill the opaque area
$image = new Imagick($imageInPath); // Create imagick object
$image->compositeImage($overlay, Imagick::COMPOSITE_DARKEN , 0, 0); // Compose the original image with the overlay
$image->writeImage( $imageOutPath ); // Save the image to the given path
?>
This snippet assumes that the first pixel (x=0, y=0) is transparent.
You can change the way to apply the overlay on the picture by using another COMPOSITE constant. You can get the full list of imagick constants here : Imagick constants
Don't forget to install Imagick library and to enable it in the php.ini file to get it work.
Related
I'm toying around with making a super simple HTML Canvas crop tool. The first thing I tested was to see if the output image would be perceptually identical to the input image.
Using this image as a source, canvas fails to maintain the smooth gradients as you can see in the image comparison I posted here (still visible despite the imgur compression). You can also replicate it in any online photo editor such as https://pixlr.com.
Is there some way to fix this?
Code snippet I am using:
const loadImageToCanvas = (file) => { // file is from input.files
const img = new Image();
img.onload = () => {
const { width, height } = img;
canvas.width = width;
canvas.height = height;
ctx.clearRect(0, 0, width, height);
ctx.drawImage(img, 0, 0);
}
img.src = URL.createObjectURL(file);
};
Two words: gamma correction. Your PNG file has a gAMA chunk of 1.0000. Web browsers are (correctly) using this information to adjust the displayed pixels for an output device having the standard sRGB gamma of 2.2. This behaviour is the same for both <canvas> and <img> elements.[1]
I don't know what viewer or conversion tool you are using to produce your imgur image, but it is either stripping or ignoring the gamma chunk.
If your image is in fact encoded with a gamma of 2.2 (and thus the gamma chunk is erroneous), you can remove the chunk with:
pngcrush -rem gAMA 1024.png 1024.nogamma.png
[1] The spec mandates this consistency. Are you really seeing different behaviour between your (correct, although using createObjectURL is unnecessary and a bad idea) code and an <img> tag?
I'm trying to send images from desk to webapp, on the desk part I have a button that takes a screen-shot then compares it with the one taken before, it calculates the difference between them then generate a new Bitmap based on this difference, then it sends this new Bitmap to the webapp which draws it on a canvas, this all happens in real-time using socket.io.
My problem is, let's assume I use Google Chrome on my screen and took a screen-shot, then I opened cmd.exe & took another screenshot, assuming that cmd.exe size is 300 * 100, and starts at half of the screen, the new generated bitmap will contains the cmd.exe screen-shots but rest of the screen (the uncover parts of Google Chrome) will be black, which is perfect for my case since i want to reduce bandwidth usage, now what I want to do, is take the difference bitmap (blob) on the JavaScript side and draw the difference on the previous screen-shot, which will make it look like I transferred the whole screen-shot, If i simply parse the blobs on the canvas, It will make a black screen with the cmd.exe on the center of it, here is my current code :
socket.on("image up", (bin) => {
var ctx = canvas[0].getContext('2d');
var img = new Image();
img.onload = function() {
ctx.drawImage(img, 0, 0);
}
var urlCreator = window.URL || window.webkitURL;
var binaryData = [];
binaryData.push(bin);
img.src = urlCreator.createObjectURL(new Blob(binaryData));
});
Any suggestions ? Thanks in advance
I have a question about saving the png to the canvas.
It saves me a picture, but it saves me just what draws. As I loaded the background that no longer sees.
var dataURL = this.canva.toDataURL();
document.getElementById(id).src = dataURL;
background images I have written out so
function leapController()
{
var tracks = new Array();
tracks[0] = {link: 'png/1.png'};
tracks[1] = {link: 'png/2.png'};
tracks[2] = {link: 'png/3.png'};
tracks[3] = {link: 'png/4.png'};
tracks[4] = {link: 'png/5.png'};
I would like to enroll my whole image, along with the background and drawing element
You give precious little code in your question, but the idea is when you're ready to save the canvas you can use Compositing to draw the background image behind the existing pixels.
To get you started ... here's some code applying the idea:
function save(desiredBackgroundIndex){
// set context compositing to draw behind existing pixels
context.globalCompositeOperation='destination-over';
// Draw the desired background on the canvas BEHIND existing pixels
// Assumes tracks[] is in scope
context.drawImage(tracks[desiredBackgroundIndex],0,0);
// always clean up! Change compositing mode back to default
context.globalCompositeOperation='source-over';
// set #id's src to the canvas's data url
document.getElementById(id).src = this.canva.toDataURL();
}
// USAGE example: add png/3.png to the canvas and save the canvas's data url to #id
save(2);
I am trying to create an application like this using JavaScript and canvas.
I have created a draft using kineticjs as the canvas library. The teeth are all png images and the lines are drawn by kinetic js using the Line object.
A friend of mine suggested I draw everything as one large svg (teeth and lines) where each tooth and line is another distinguished path load it on canvas with distinguished id's and manipulate them using the id's.
I tried reading the kineticjs docs and fabricjs object but didn't find something like that. I mean having the svg stored and loading it on canvas using fabricjs or kineticjs. Then the library should parse it and create the svg on canvas which I can manipulate them by id's.
Is it even possible? I am very new to this svg graphics. Is it even possible the way I am thinking it?
I am currently loading Teeth as Images using KineticJS
Periodontogram.prototype.initializeImageObjects = function (){
var obj = this;
if (this.DEBUG){
console.log("initializing ImageObjects:")
}
var check = function (item){return item !== undefined;}
var url = this.options.url;
var imageObj;
var image;
this.options.imageFilenames.forEach(function (filename, index){
if (obj.DEBUG){
console.log("Loading "+filename+" image");
}
var src = url+'/'+filename;
imageObj = new Image();
imageObj.src = src;
imageObj.onload = function (){
if (obj.DEBUG){
console.log("Image "+src+" successfully loaded");
}
image = new Kinetic.Image({
x:0,
y:0,
id: filename.split('.')[0],
width: this.width,
height: this.height,
image:this
});
obj.imageObjects[index] = this;
obj.teethImages[index] = image;
if (obj.imageObjects.filter(check).length === obj.options.imageFilenames.length ) {
if (obj.DEBUG){
console.log("All Images loaded successfully");
console.log("ready to call rest methods")
}
obj.positionImages();
obj.createLineGroups();
obj.createLabelGroups()
obj.createStage();
obj.drawOnScreen();
$("#"+obj.container).width(obj.stage.getWidth());
$("#"+obj.container).height(obj.stage.getHeight ());
obj.callback();
$.event.trigger({
type:'finishLoading',
obj: obj
});
}
};
});
}
I want all this be replaced by loading a whole svg, that consist of teeth and lines.
FabricJS will convert, decompose & recompose an existing single SVG URL/string into a Fabric.PathGroup. Then you can use mySvgGroup.getObjects() to fetch the individual tooth paths which you can manipulate as desired.
http://fabricjs.com/fabric-intro-part-3/#deserialization
KineticJS will accept an individual SVG tooth's path data and display it on canvas.
http://kineticjs.com/docs/Kinetic.Path.html
Still, it's probably more flexible to create individual SVG paths for each tooth and feed those individual SVGs into either KineticJS or FabricJS.
Then you can manipulate them by id's as you desire:
Place each individual tooth using [x,y] coordinates,
Resize without pixelization,
Put each tooth in a Group and make annotations/drawings onto the tooth,
I'm trying to produce the same base64 data for an image file in both JavaScript and in Ruby. Unfortunately both are outputting two very different values.
In Ruby I do this:
Base64.encode64(File.binread('test.png'));
And then in JavaScript:
var image = new Image();
image.src = 'http://localhost:8000/test.png';
$(image).load(function() {
var canvas, context, base64ImageData;
canvas = document.createElement('canvas');
context = canvas.getContext('2d');
canvas.width = this.width;
canvas.height = this.height;
context.drawImage(this, 0, 0);
imageData = canvas.toDataURL('image/png').replace(/data:image\/[a-z]+;base64,/, '');
console.log(imageData);
});
Any idea why these outputs are different?
When you load the image in Ruby the binary file without any modifications will be encoded directly to base-64.
When you load an image in the browser it will apply some processing to the image before you will be able to use it with canvas:
ICC profile will be applied (if the image file contains that)
Gamma correction (where supported)
By the time you draw the image to canvas, the bitmap values has already been changed and won't necessarily be identical to the bitmap that was encoded before loading it as image (if you have an alpha channel in the file this may affect the color values when drawn to canvas - canvas is a little peculiar at this..).
As the color values are changed the resulting string from canvas will naturally also be different, before you even get to the stage of re-encoding the bitmap (as PNG is loss-less the encoding/compressing should be fairly identical, but factors may exist depending on the browser implementation that will influence that as well. to test, save out a black unprocessed canvas as PNG and compare with a similar image from your application - all values should be 0 incl. alpha and at the same size of course).
The only way to avoid this is to deal with the binary data directly. This is of course a bit overkill (in general at least) and a relative slow process in a browser.
A possible solution that works in some cases, is to remove any ICC profile from the image file. To save an image from Photoshop without ICC choose "Save for web.." in the file menu.
The browser is re-encoding the image as you save the canvas.
It does not generate an identical encoding to the file you rendered.
So I actually ended up solving this...
Fortunately I am using imgcache.js to cache images in the local filesystem using the FileSystem API. My solution is to use this API (and imgcache.js makes it easy) to get the base64 data from the actual cached copy of the file. The code looks like this:
var imageUrl = 'http://localhost:8000/test.png';
ImgCache.init(function() {
ImgCache.cacheFile(imageUrl, function() {
ImgCache.getCachedFile(imageUrl, function(url, fileEntry) {
fileEntry.file(function(file) {
var reader = new FileReader();
reader.onloadend = function(e) {
console.log($.md5(this.result.replace(/data:image\/[a-z]+;base64,/, '')));
};
reader.readAsDataURL(file);
});
});
});
});
Also, and very importantly, I had to remove line breaks from the base64 in Ruby:
Base64.encode64(File.binread('test.png')).gsub("\n", '');