package com.idega.block.image.presentation; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.IOException; import java.math.BigInteger; import java.rmi.RemoteException; import java.sql.SQLException; import javax.ejb.FinderException; import javax.media.jai.JAI; import javax.media.jai.PlanarImage; import com.idega.block.image.business.ImageEncoder; import com.idega.block.image.business.ImageProcessor; import com.idega.block.image.business.ImageProvider; import com.idega.block.image.data.ImageEntity; import com.idega.block.image.data.ImageProcessJob; import com.idega.business.IBOLookup; import com.idega.core.file.data.ICFile; import com.idega.core.file.data.ICFileHome; import com.idega.idegaweb.IWCacheManager; import com.idega.idegaweb.IWMainApplication; import com.idega.presentation.IWContext; import com.idega.presentation.Image; import com.idega.presentation.text.Link; import com.idega.util.caching.Cache; import com.sun.media.jai.codec.MemoryCacheSeekableStream; /** * * * Title: idegaWeb * Description: AdvancedImage represents a image and extends * {@link com.idega.presentation.Image Image}. * * In contrast to the Image class changes of the size by the methods * setHeight and setWith are not performed by adding corresponding values * and commands to the print method but by creating a new image * with the desired size. The new image with the desired size is sent to the client. * Therefore the image is not resized on the client side but * on the server side. * * The height and the width of the image can be set by using * the setHeight and setWidth methods of the Image class. * * The ImageEncoder service bean is used to create the new image. * The new created image is uploaded into the database * into a branch of the original image. The type of the new image is not * necessary equal to the type of the original image. The * ImageEncoder is responsible for changing the type. * (e.g. bitmap is transformed to jpeg). * * An instance of this class represents therefore the original image and all derived * images: Depending on the values of the height and the width value the * corresponding image is used by the print method. A new image is only * created if that size was never created before otherwise the desired * image is fetched from the database or cache. * This means that an access to * all modified images and especially to the original image is always possible. * * To print the original image when this image is set to a different size the * {@link com.idega.block.image.presentation.AdvancedImageWrapper AdvancedImageWrapper} * can be used. Use the constructor AdvancedImageWrapper(this) and the wrapper represents * the original image, especially the original image is printed if the wrapper is printed. * * * Copyright: Copyright (c) 2003 * Company: idega software * @author <a href="mailto:thomas@idega.is">Thomas Hilbig</a> * @version 1.0 */ public class AdvancedImage extends Image { /** Folder where the modified images are stored */ public static final String MODIFIED_IMAGES_FOLDER = "modified_images"; /** border around the image in the popup window */ private static final String BORDER = "90"; /** cached value not an attribute */ private ImageEntity imageEntity; /** cached value not an attribute */ private PlanarImage originalImage; /** cached value not an attribute */ private String realPathToImage; /** id of the original image. * The id is stored because during the execution of the print method * the id variable of the super class must be set to the id of the modified image * when a modified image is printed. See print method of this class. */ private int originalImageId; private int modifiedImageId = -1; private int widthOfModifiedImage = 0; private int heightOfModifiedImage = 0; /** flag to show if the image should be enlarged or not */ private boolean enlargeIfNecessary = false; /** flag to show if the image should keep it�s proportion */ private boolean scaleProportional = true; public AdvancedImage(int imageId) throws SQLException{ super(imageId); this.originalImageId = imageId; } public AdvancedImage(int imageId, String name) throws SQLException{ super(imageId, name); this.originalImageId = imageId; } public void main(IWContext iwc) { super.main(iwc); scaleImage(iwc); } public void print(IWContext iwc) throws Exception { //TODO Eiki show a message that the image is being prepared if the id is -1 //or have it as a setting // set id temporary to id of the modified image if (this.modifiedImageId > -1) { setImageID(this.modifiedImageId); super.print(iwc); // set old value setImageID(this.originalImageId); } else{ printOriginalImage(iwc); } } public void printOriginalImage(IWContext iwc) throws Exception { super.print(iwc); } public void setEnlargeProperty(boolean enlargeIfNecessary) { this.enlargeIfNecessary = enlargeIfNecessary; } public void setScaleProportional(boolean scaleProportional) { this.scaleProportional = scaleProportional; } /* public void setImageToOpenInPopUp(IWContext iwc) { try { String width = Integer.toString(getWidthOfOriginalImage(iwc)); String height = Integer.toString(getHeightOfOriginalImage(iwc)); this.setOnClick("img_wnd=window.open('"+getMediaURL()+"','','width="+width+",height="+height+",left='+((screen.width/2)-50)+',top='+((screen.height/2)-50)+',resizable=yes,scrollbars=no'); doopen('"+getMediaURL()+"'); return true;"); } catch (Exception ex) { System.err.println(ex.getMessage()); } } */ private void scaleImage(IWContext iwc) { try { this.modifiedImageId = createAndStoreImage(iwc); //commented because we want the browser to know the width and height (faster browser rendering) //even when the image has not been scaled (otherwise the layout is destroyed)... // remove these attributes to prevent scaling by the browser client // removeMarkupAttribute(HEIGHT); // removeMarkupAttribute(WIDTH); // } catch (Exception ex) { // set modified image id back this.modifiedImageId = -1; System.err.println("Image could not be modified. Message was: "+ ex.getMessage()); } } private boolean checkAndCalculateNewWidthAndHeight(IWContext iwc) throws Exception { String heightString = getHeight(); String widthString = getWidth(); if (heightString == null || widthString == null) { // image must be not modified return false; } int setHeight = Integer.parseInt(heightString); int setWidth = Integer.parseInt(widthString); if ((setHeight <= 0) && (setWidth <= 0)) { // image must not be modified return false; } // ...there are new settings for the height and the width // calculate now the new values for the modified image.... // first assumption: image must not be modified: this.heightOfModifiedImage = 0; this.widthOfModifiedImage = 0; boolean imageMustBeModified = false; // get values of the original image int heightOfOriginalImage = getHeightOfOriginalImage(iwc); int widthOfOriginalImage = getWidthOfOriginalImage(iwc); /* modify height, if + desired height is defined + image should be enlarged (if it is smaller than the desired height) + imgage is too large for the desired heigth */ if ((heightOfOriginalImage < setHeight && this.enlargeIfNecessary) || (heightOfOriginalImage > setHeight)) { this.heightOfModifiedImage = setHeight; imageMustBeModified = true; } else { this.heightOfModifiedImage = heightOfOriginalImage; } /* modify width, if + desired width is defined + image should be enlarged (if it is smaller than the desired width) + imgage is too large for the desired width */ if ((widthOfOriginalImage < setWidth && this.enlargeIfNecessary) || (widthOfOriginalImage > setWidth)) { this.widthOfModifiedImage = setWidth; imageMustBeModified = true; } else { this.widthOfModifiedImage = widthOfOriginalImage; } // resize the image proportional if desired if (imageMustBeModified && this.scaleProportional) { BigInteger wTable = BigInteger.valueOf(setWidth); BigInteger hTable = BigInteger.valueOf(setHeight); BigInteger wImage = BigInteger.valueOf(widthOfOriginalImage); BigInteger hImage = BigInteger.valueOf(heightOfOriginalImage); // start calculation with big integers BigInteger wImagehTable = wImage.multiply(hTable); BigInteger hImagewTable = hImage.multiply(wTable); if (hImagewTable.compareTo(wImagehTable) > 0) { // set height of modified image to height of the cell this.heightOfModifiedImage = setHeight; this.widthOfModifiedImage = wImagehTable.divide(hImage).intValue(); } else { // set width of modified image to width of the cell this.widthOfModifiedImage = setWidth; this.heightOfModifiedImage = hImagewTable.divide(wImage).intValue(); } // sometimes the new values equal to the original values: // in this case do not modify the image if (this.widthOfModifiedImage == widthOfOriginalImage && this.heightOfModifiedImage == heightOfOriginalImage) { // do not modify the image this.widthOfModifiedImage = 0; this.heightOfModifiedImage = 0; return false; } } // end of calculation of the values width and height of the modified image return imageMustBeModified; } /** Gets height of original image. */ public synchronized int getHeightOfOriginalImage(IWContext iwc) throws Exception{ String heightOfOriginalImage = getImageEntity(iwc).getHeight(); if (heightOfOriginalImage == null) { PlanarImage image = getOriginalImage(iwc); int height = image.getHeight(); int width = image.getWidth(); setHeightAndWidthOfOriginalImageAtEntity(width, height, iwc); return height; } return Integer.parseInt(heightOfOriginalImage); } /** Gets width of the original image */ public synchronized int getWidthOfOriginalImage(IWContext iwc) throws Exception { String widthOfOriginalImage = getImageEntity(iwc).getWidth(); if (widthOfOriginalImage == null) { PlanarImage image = getOriginalImage(iwc); int height = image.getHeight(); int width = image.getWidth(); setHeightAndWidthOfOriginalImageAtEntity(width, height, iwc); return width; } return Integer.parseInt(widthOfOriginalImage); } public String getHeight() { String height = super.getHeight(); // height is set? // In this case get the height that was set by the setHeight method of the super class! if (height != null) { return height; } // has the height been set before? // remember: to prevent that the image is resized on the client side // the height attribute was deleted after creating the modified image // but the value is stored in the // heightOfModifiedImage variable. // see: scale() method of this class // Therefore: if (this.heightOfModifiedImage > 0) { return Integer.toString(this.heightOfModifiedImage); } return null; } public String getWidth() { String width = super.getWidth(); // width is set? // In this case get the width that was set by the setWidth method of the super class! if (width != null) { return width; } // has the width been set before? // remember: to prevent that the image is resized on the client side // the width attribute was deleted after creating the modified image // but the value is stored in the // widthOfModifiedImage variable. // see: scale() method of this class // Therefore: if (this.widthOfModifiedImage > 0) { return Integer.toString(this.widthOfModifiedImage); } return null; } /** Adds a link to this image to a popup window that the original version of this image shows */ public void setLinkToDisplayWindow(IWContext iwc, int imageNumber) { Link link = new Link(); String imageID = Integer.toString(this.originalImageId); String widthString; String heightString; try { widthString = Integer.toString(getWidthOfOriginalImage(iwc)); heightString = Integer.toString(getHeightOfOriginalImage(iwc)); // if an exception occurs the width and height parameters are not set. // In this case default values are used. link.setParameter(ImageDisplayWindow.PARAMETER_BORDER, BORDER); link.setParameter(ImageDisplayWindow.PARAMETER_WIDTH, widthString); link.setParameter(ImageDisplayWindow.PARAMETER_HEIGHT, heightString); link.setParameter(ImageDisplayWindow.PARAMETER_IMAGE_NUMBER, String.valueOf(imageNumber)); } catch (Exception ex) { // do nothing // default values of height and width (regarding a pop up window) will be used } String title = getResourceBundle(iwc).getLocalizedString("image","Image"); link.setParameter(ImageDisplayWindow.PARAMETER_TITLE, title); // set imageID of the original image link.setParameter(ImageDisplayWindow.PARAMETER_IMAGE_ID,imageID); link.setParameter(ImageDisplayWindow.PARAMETER_INFO, getName()); link.setWindowToOpen(ImageDisplayWindow.class); setImageZoomLink(link); setImageLinkZoomView(); } /** * Method setHeightAndWidthOfOriginalImageAtEntity. * @param width * @param height */ private void setHeightAndWidthOfOriginalImageAtEntity(int width, int height, IWContext iwc) throws Exception { ImageProvider imageProvider = getImageProvider(iwc); ImageEntity entity = getImageEntity(iwc); imageProvider.setHeightAndWidthOfOriginalImageToEntity(width, height, entity); } private ImageEntity getImageEntity(IWContext iwc) { if (this.imageEntity == null) { setRealPathToImageAndImageEntity(iwc); } return this.imageEntity; } private String getRealPathToImage(IWContext iwc) { if (this.realPathToImage == null) { setRealPathToImageAndImageEntity(iwc); } return this.realPathToImage; } private void setRealPathToImageAndImageEntity(IWContext iwc) { Cache cachedImage = getCachedImage(iwc, this.originalImageId); this.realPathToImage = cachedImage.getRealPathToFile(); this.imageEntity = (ImageEntity) cachedImage.getEntity(); } private PlanarImage getOriginalImage(IWContext iwc) throws Exception { if (this.originalImage != null) { return this.originalImage; } MemoryCacheSeekableStream stream = new MemoryCacheSeekableStream( new BufferedInputStream(new FileInputStream(getRealPathToImage(iwc)))); this.originalImage = JAI.create("stream", stream); stream.close(); return this.originalImage; } private Cache getCachedImage(IWContext iwc, int imageId) { // this method is similar to the private getImage() method of the super class Image IWMainApplication iwma = iwc.getIWMainApplication(); return IWCacheManager.getInstance(iwma).getCachedBlobObject(com.idega.block.image.data.ImageEntity.class.getName(),imageId,iwma); } private int createAndStoreImage(IWContext iwc) throws Exception { // get image encoder ImageEncoder imageEncoder = getImageEncoder(iwc); // get mime type Cache cachedImage = getCachedImage(iwc, this.originalImageId); ImageEntity imageEntity = (ImageEntity) cachedImage.getEntity(); String mimeType = imageEntity.getMimeType(); // is it necessary to convert the image? if (!checkAndCalculateNewWidthAndHeight(iwc)){ // okay: the desired width and height is the same as the original image // check if the image would be converted to another type if (imageEncoder.isInputTypeEqualToResultType(mimeType)) { // do nothing, use the original image ----- return -1; }else{ // convert the original image using the same size //e.g. bitmap to jpeg conversion because of load size this.heightOfModifiedImage = getHeightOfOriginalImage(iwc); this.widthOfModifiedImage = getWidthOfOriginalImage(iwc); } } //set the xml width and height setHeight(this.heightOfModifiedImage); setWidth(this.widthOfModifiedImage); // look up the file extension of the result file the image encoder returns // for this mime type String extension = imageEncoder.getResultFileExtensionForInputMimeType(mimeType); if (ImageEncoder.INVALID_FILE_EXTENSION.equals(extension)){ throw new IOException("ImageEncoder do not known this mime type:"+mimeType); } String nameOfModifiedImage = getNameOfModifiedImageWithExtension(this.widthOfModifiedImage, this.heightOfModifiedImage ,extension, imageEntity); //TODO if the image exists on HARD DISK then set the Image url to that path //otherwise get the id from the database. // setURL(path); // check if the image already exists and return the value if found int imageID = getImageIDByName(nameOfModifiedImage); if ( imageID > -1) { // nothing to do, use the already existing modified image --- return imageID; } else{ //------------------------------------------------------------------------------------// // ------------------ image has to be processed --------------------------------------- // get the processer // create a image process job ImageProcessJob job = new ImageProcessJob(); job.setCachedImage(cachedImage); job.setNewExtension(extension); job.setNewWidth(this.widthOfModifiedImage); job.setNewHeight(this.heightOfModifiedImage); job.setJobKey(nameOfModifiedImage); ImageProcessor processor = ImageProcessor.getInstance(iwc); processor.addImageProcessJobToQueu(job); //the scaled image is not ready yet return -1; } } private int getImageIDByName(String name) { ICFileHome icFileHome = (ICFileHome) com.idega.data.IDOLookup.getHomeLegacy(ICFile.class); ICFile icFile; try { icFile = icFileHome.findByFileName(name); } catch (FinderException e) { return -1; } return ((Integer)icFile.getPrimaryKey()).intValue(); } private ImageEncoder getImageEncoder(IWContext iwc) throws RemoteException{ return (ImageEncoder) IBOLookup.getServiceInstance(iwc,ImageEncoder.class); } private ImageProvider getImageProvider(IWContext iwc) throws RemoteException { return (ImageProvider) IBOLookup.getServiceInstance(iwc, ImageProvider.class); } private String getNameOfModifiedImageWithExtension(int width, int height, String extension, ImageEntity entity) { String name = getName(); int pointPosition = name.lastIndexOf('.'); int length = name.length(); // cut extension (name.a name.ab name.abc but not name.abcd) if ( (pointPosition > 0) && pointPosition > (length - 5)) { name = name.substring(0,pointPosition); } StringBuffer nameOfImage = new StringBuffer(); // add new extension nameOfImage.append(entity.getPrimaryKey()); nameOfImage.append(width).append("_").append(height) .append("_").append(name) .append(".").append(extension); return nameOfImage.toString(); } }