/******************************************************************************* * Copyright (c) 2010, 2015 EclipseSource and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * EclipseSource - initial API and implementation * Frank Appel - replaced singletons and static fields (Bug 337787) ******************************************************************************/ package org.eclipse.swt.internal.graphics; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.zip.CRC32; import org.eclipse.rap.rwt.RWT; import org.eclipse.rap.rwt.internal.util.SharedInstanceBuffer; import org.eclipse.rap.rwt.internal.util.SharedInstanceBuffer.InstanceCreator; import org.eclipse.swt.SWT; import org.eclipse.swt.SWTException; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.ImageLoader; import org.eclipse.swt.graphics.RGB; public class InternalImageFactory { private final SharedInstanceBuffer<String, InternalImage> cache; public InternalImageFactory() { cache = new SharedInstanceBuffer<String, InternalImage>(); } public InternalImage findInternalImage( final String fileName ) { return cache.get( fileName, new InstanceCreator<String, InternalImage>() { public InternalImage createInstance( String fileName ) { return createInternalImage( fileName ); } } ); } public InternalImage findInternalImage( InputStream stream ) { final BufferedInputStream bufferedStream = new BufferedInputStream( stream ); final ImageData imageData = readImageData( bufferedStream ); final String path = createGeneratedImagePath( imageData ); return cache.get( path, new InstanceCreator<String, InternalImage>() { public InternalImage createInstance( String path ) { return createInternalImage( path, bufferedStream, imageData ); } } ); } public InternalImage findInternalImage( final ImageData imageData ) { final String path = createGeneratedImagePath( imageData ); return cache.get( path, new InstanceCreator<String, InternalImage>() { public InternalImage createInstance( String path ) { InputStream stream = createInputStream( imageData ); return createInternalImage( path, stream, imageData ); } } ); } InternalImage findInternalImage( String key, final InputStream inputStream ) { return cache.get( key, new InstanceCreator<String, InternalImage>() { public InternalImage createInstance( String key ) { BufferedInputStream bufferedStream = new BufferedInputStream( inputStream ); ImageData imageData = readImageData( bufferedStream ); String path = createGeneratedImagePath( imageData ); return createInternalImage( path, bufferedStream, imageData ); } } ); } static ImageData readImageData( InputStream stream ) throws SWTException { //////////////////////////////////////////////////////////////////////////// // TODO: [fappel] Image size calculation and resource registration both // read the input stream. Because of this I use a workaround // with a BufferedInputStream. Resetting it after reading the // image size enables the ResourceManager to reuse it for // registration. Note that the order is crucial here, since // the ResourceManager seems to close the stream (shrug). // It would be nice to find a solution without reading the // stream twice. stream.mark( Integer.MAX_VALUE ); ImageData result = new ImageData( stream ); try { stream.reset(); } catch( IOException shouldNotHappen ) { String msg = "Could not reset input stream after reading image"; throw new RuntimeException( msg, shouldNotHappen ); } return result; } static InputStream createInputStream( ImageData imageData ) { ImageLoader imageLoader = new ImageLoader(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); imageLoader.data = new ImageData[] { imageData }; imageLoader.save( outputStream, getOutputFormat( imageData ) ); byte[] bytes = outputStream.toByteArray(); return new ByteArrayInputStream( bytes ); } private static InternalImage createInternalImage( String fileName ) { InternalImage result; try { FileInputStream stream = new FileInputStream( fileName ); try { result = createInternalImage( stream ); } finally { stream.close(); } } catch( IOException ioe ) { throw new SWTException( SWT.ERROR_IO, ioe.getMessage() ); } return result; } private static InternalImage createInternalImage( InputStream stream ) { InputStream bufferedStream = new BufferedInputStream( stream ); ImageData imageData = readImageData( bufferedStream ); String path = createGeneratedImagePath( imageData ); return createInternalImage( path, bufferedStream, imageData ); } private static InternalImage createInternalImage( String path, InputStream stream, ImageData imageData ) { RWT.getResourceManager().register( path, stream ); return new InternalImage( path, imageData.width, imageData.height, false ); } private static int getOutputFormat( ImageData imageData ) { int result = imageData.type; if( imageData.type == SWT.IMAGE_UNDEFINED ) { result = SWT.IMAGE_PNG; } return result; } private static String createGeneratedImagePath( ImageData data ) { int outputFormat = getOutputFormat( data ); String extension; switch( outputFormat ) { case SWT.IMAGE_BMP: case SWT.IMAGE_BMP_RLE: extension = ".bmp"; break; case SWT.IMAGE_GIF: extension = ".gif"; break; case SWT.IMAGE_JPEG: extension = ".jpg"; break; case SWT.IMAGE_TIFF: extension = ".tif"; break; case SWT.IMAGE_ICO: extension = ".ico"; break; case SWT.IMAGE_PNG: default: extension = ".png"; break; } String hash = getHash( data ) + extension; return "generated/" + hash; } /* * [cm] Compute a CRC32 value using all of the parts of the ImageData. For * parts that may be null, a unique salt is added to avoid collisions in rare * cases. There is a possibility that, for instance, the alphaData is set in * one image but not the maskData. Then in a second image, the maskData is set * to the same thing as the previous image, but no alphaData is set. In this * case there would be a collision if no other information is added. */ private static String getHash( ImageData imageData ) { CRC32 crc32 = new CRC32(); if( imageData.data != null ) { crc32.update( 1 ); crc32.update( imageData.data ); } if( imageData.alphaData != null ) { crc32.update( 2 ); crc32.update( imageData.alphaData ); } if( imageData.maskData != null ) { crc32.update( 3 ); crc32.update( imageData.maskData ); } if( imageData.palette != null ) { crc32.update( 4 ); if( imageData.palette.isDirect ) { crc32.update( 5 ); crc32.update( imageData.palette.redMask ); crc32.update( imageData.palette.greenMask ); crc32.update( imageData.palette.blueMask ); } else { crc32.update( 6 ); RGB[] rgb = imageData.palette.getRGBs(); for( int i = 0; i < rgb.length; i++ ) { crc32.update( rgb[ i ].red ); crc32.update( rgb[ i ].green ); crc32.update( rgb[ i ].blue ); } } } crc32.update( imageData.alpha ); crc32.update( imageData.transparentPixel ); crc32.update( imageData.type ); crc32.update( imageData.bytesPerLine ); crc32.update( imageData.scanlinePad ); crc32.update( imageData.maskPad ); crc32.update( imageData.x ); crc32.update( imageData.y ); crc32.update( imageData.width ); crc32.update( imageData.height ); crc32.update( imageData.depth ); crc32.update( imageData.delayTime ); crc32.update( imageData.disposalMethod ); return Long.toHexString( crc32.getValue() ); } }