/* * Copyright 2013 cruxframework.org. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package org.cruxframework.crux.core.client.image; import org.cruxframework.crux.core.client.file.Blob; import org.cruxframework.crux.core.client.file.FileReader; import org.cruxframework.crux.core.client.file.FileReader.ReaderStringCallback; import org.cruxframework.crux.core.client.file.URL; import org.cruxframework.crux.core.client.utils.FileUtils; import com.google.gwt.canvas.dom.client.Context2d; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.dom.client.CanvasElement; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.ImageElement; import com.google.gwt.dom.client.PartialSupport; /** * An Image processor * @author Thiago da Rosa de Bustamante * @author Samuel Almeida Cardoso */ @PartialSupport public class ImageProcessor { private CanvasElement canvas = Document.get().createCanvasElement().cast(); private NativeImage image; /** * Constructor */ protected ImageProcessor(){} /** * Export content image as a GIF file. * @return */ public Blob asGif() { return exportImage("image/gif"); } /** * Export content image as a JPEG file. * @param quality JPEG quality. It ranges from 0.0 to 1.0 * @return */ public Blob asJpeg(double quality) { return FileUtils.fromDataURI(toJpegURL(canvas, quality)); } /** * Export content image as a PNG file. * @return */ public Blob asPng() { return exportImage("image/png"); } /** * Ensure a max size for the image * @param maxHeight * @param maxWidth * @param keepProportions */ public void ensureMaxSize(int maxWidth, int maxHeight, boolean keepProportions) { int width = canvas.getWidth(); int height = canvas.getHeight(); if (keepProportions) { if (width > height) { if (width > maxWidth) { height = Math.round(height *= maxWidth / (double) width); width = maxWidth; } } else { if (height > maxHeight) { width = Math.round(width *= maxHeight / (double) height); height = maxHeight; } } } else { width = Math.min(width, maxWidth); height = Math.min(height, maxHeight); } resize(width, height); } /** * @return the canvas. */ public CanvasElement getCanvas() { return canvas; } /** * @return the image height. */ public int getHeight() { if(canvas != null) { return canvas.getHeight(); } return 0; } public NativeImage getImage() { return image; } /** * @return the image width. */ public int getWidth() { if(canvas != null) { return canvas.getWidth(); } return 0; } /** * @return true if the image is a landscape image. */ public boolean isLandscape() { return !isPortrait(); } /** * @return true if the image is a portrait image. */ public boolean isPortrait() { if (canvas.getWidth() > canvas.getHeight()) { return false; } else { return true; } } /** * Loads an image to the processor. * @param file Image File * @param handler Called when image is completely loaded */ public void loadImage(final Blob image, final ImageLoadHandler handler) { if(FileReader.isSupported()) { FileReader.createIfSupported().readAsDataURL(image, new ReaderStringCallback() { @Override public void onComplete(String result) { loadImage(result, handler); } }); } else if (URL.isSupported()) { loadImage(URL.createObjectURL(image), handler); } else { handler.onError(); } } /** * Loads an image to the processor. * @param url Image URL * @param handler Called whem image is completely loaded */ public void loadImage(String url, final ImageLoadHandler handler) { final NativeImage newImage = NativeImage.create(); newImage.setLoadHandler(new ImageLoadHandler() { @Override public void onError() { if (handler != null) { handler.onError(); } } @Override public void onLoad(ImageProcessor processor) { loadImageToCanvas(newImage, newImage.getWidth(), newImage.getHeight()); if (handler != null) { handler.onLoad(processor); } } }, this); newImage.setSrc(url); } /** * Resize the internal image * @param width * @param height */ public void resize(int width, int height) { CanvasElement canvas = Document.get().createCanvasElement().cast(); canvas.setWidth(width); canvas.setHeight(height); canvas.getContext2d().drawImage(this.canvas, 0, 0, this.canvas.getWidth(), this.canvas.getHeight(), 0, 0, width, height); this.canvas = canvas; } /** * Rotates an image to a certanly degree. * @param degrees the degree of rotation. sample values: 90, 180, 270. */ public void rotate(int degrees) { Context2d context = canvas.getContext2d(); //clear canvas canvas.getContext2d().clearRect(0,0,canvas.getWidth(), canvas.getHeight()); // move to the center of the canvas context.translate(canvas.getWidth()/2,canvas.getHeight()/2); // rotate the canvas to the specified degrees context.rotate(degrees*Math.PI/180); // draw the image // since the context is rotated, the image will be rotated also context.drawImage(image.asImageElement(),-image.getWidth()/2,image.getHeight()/2); //restore previous rotation context.rotate(-degrees*Math.PI/180); //translate back the image context.translate(-canvas.getWidth()/2,-canvas.getHeight()/2); } /** * Save the state of the operation: resize, rotate and so on. */ public void saveOperation() { canvas.getContext2d().save(); } /** * Undo the last operation: resize, rotate and so on. */ public void undoLastOperation() { canvas.getContext2d().restore(); } private Blob exportImage(String imageType) { return FileUtils.fromDataURI(canvas.toDataUrl(imageType)); } private void loadImageToCanvas(NativeImage image, int width, int height) { this.image = image; canvas.setWidth(width); canvas.setHeight(height); canvas.getContext2d().drawImage(image.asImageElement(), 0, 0, width, height); } private native String toJpegURL(CanvasElement canvas, double quality)/*-{ return canvas.toDataURL("image/jpeg", quality); }-*/; /** * If the current browser supports, create a new ImageProcessor * @return */ public static ImageProcessor createIfSupported() { if (isSupported()) { return new ImageProcessor(); } return null; } /** * Create ImageProcessor instance and automatically loads the image. * @param file Image File * @param handler Called whem image is completely loaded */ public static void createIfSupportedAndLoadImage(final Blob image, final ImageCreateAndLoadHandler handler) { ImageProcessor imageProcessor = createIfSupported(); if(imageProcessor == null) { handler.onNotSupported(); return; } imageProcessor.loadImage(image, handler); } /** * Create ImageProcessor instance and automatically loads the image. * @param url Image URL * @param handler Called whem image is completely loaded */ public static void createIfSupportedAndLoadImage(String url, final ImageCreateAndLoadHandler handler) { ImageProcessor imageProcessor = createIfSupported(); if(imageProcessor == null) { handler.onNotSupported(); return; } imageProcessor.loadImage(url, handler); } /** * Check if browser supports this feature * @return */ public static boolean isSupported() { return Blob.isSupported() && FileReader.isSupported(); } public static interface ImageCreateAndLoadHandler extends ImageLoadHandler { void onNotSupported(); } public static interface ImageLoadHandler { void onError(); void onLoad(ImageProcessor processor); } public static class NativeImage extends JavaScriptObject { protected NativeImage(){} public final native ImageElement asImageElement()/*-{ return this; }-*/; public final native int getHeight()/*-{ return this.height; }-*/; public final native int getWidth()/*-{ return this.width; }-*/; public final native void setLoadHandler(ImageLoadHandler handler, ImageProcessor processor)/*-{ this.onload = function(){ handler.@org.cruxframework.crux.core.client.image.ImageProcessor.ImageLoadHandler::onLoad(Lorg/cruxframework/crux/core/client/image/ImageProcessor;)(processor); }; this.onerror = function(){ handler.@org.cruxframework.crux.core.client.image.ImageProcessor.ImageLoadHandler::onError()(); }; }-*/; public final native void setSrc(String url)/*-{ this.src = url; }-*/; public static native NativeImage create()/*-{ return new Image(); }-*/; } }