| /******************************************************************************* |
| * Copyright (c) 2019 Red Hat and others. All rights reserved. |
| * The contents of this file are made available under the terms |
| * of the GNU Lesser General Public License (LGPL) Version 2.1 that |
| * accompanies this distribution (lgpl-v21.txt). The LGPL is also |
| * available at http://www.gnu.org/licenses/lgpl.html. If the version |
| * of the LGPL at http://www.gnu.org is different to the version of |
| * the LGPL accompanying this distribution and there is any conflict |
| * between the two license versions, the terms of the LGPL accompanying |
| * this distribution shall govern. |
| * |
| * Contributors: |
| * Red Hat - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.swt.graphics; |
| |
| |
| import java.io.*; |
| import java.util.*; |
| |
| import org.eclipse.swt.*; |
| import org.eclipse.swt.internal.*; |
| import org.eclipse.swt.internal.gtk.*; |
| import org.eclipse.swt.internal.image.*; |
| |
| /** |
| * Instances of this class are used to load images from, |
| * and save images to, a file or stream. |
| * <p> |
| * Currently supported image formats are: |
| * </p><ul> |
| * <li>BMP (Windows or OS/2 Bitmap)</li> |
| * <li>ICO (Windows Icon)</li> |
| * <li>JPEG</li> |
| * <li>GIF</li> |
| * <li>PNG</li> |
| * <li>TIFF</li> |
| * </ul> |
| * <code>ImageLoaders</code> can be used to: |
| * <ul> |
| * <li>load/save single images in all formats</li> |
| * <li>load/save multiple images (GIF/ICO/TIFF)</li> |
| * <li>load/save animated GIF images</li> |
| * <li>load interlaced GIF/PNG images</li> |
| * <li>load progressive JPEG images</li> |
| * </ul> |
| * |
| * <p> |
| * NOTE: <code>ImageLoader</code> is implemented in Java on some platforms, which has |
| * certain performance implications. Performance and memory sensitive applications may |
| * benefit from using one of the constructors provided by <code>Image</code>, as these |
| * are implemented natively.</p> |
| * |
| * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: ImageAnalyzer</a> |
| * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> |
| */ |
| public class ImageLoader { |
| |
| /** |
| * the array of ImageData objects in this ImageLoader. |
| * This array is read in when the load method is called, |
| * and it is written out when the save method is called |
| */ |
| public ImageData[] data; |
| |
| /** |
| * the width of the logical screen on which the images |
| * reside, in pixels (this corresponds to the GIF89a |
| * Logical Screen Width value) |
| */ |
| public int logicalScreenWidth; |
| |
| /** |
| * the height of the logical screen on which the images |
| * reside, in pixels (this corresponds to the GIF89a |
| * Logical Screen Height value) |
| */ |
| public int logicalScreenHeight; |
| |
| /** |
| * the background pixel for the logical screen (this |
| * corresponds to the GIF89a Background Color Index value). |
| * The default is -1 which means 'unspecified background' |
| * |
| */ |
| public int backgroundPixel; |
| |
| /** |
| * the number of times to repeat the display of a sequence |
| * of animated images (this corresponds to the commonly-used |
| * GIF application extension for "NETSCAPE 2.0 01"). |
| * The default is 1. A value of 0 means 'display repeatedly' |
| */ |
| public int repeatCount; |
| |
| /** |
| * This is the compression used when saving jpeg and png files. |
| * <p> |
| * When saving jpeg files, the value is from 1 to 100, |
| * where 1 is very high compression but low quality, and 100 is |
| * no compression and high quality; default is 75. |
| * </p><p> |
| * When saving png files, the value is from 0 to 3, but they do not impact the quality |
| * because PNG is lossless compression. 0 is uncompressed, 1 is low compression and fast, |
| * 2 is default compression, and 3 is high compression but slow. |
| * </p> |
| * |
| * @since 3.8 |
| */ |
| public int compression; |
| |
| /** |
| * If the 29th byte of the PNG file is not zero, then it is interlaced. |
| */ |
| final static int PNG_INTERLACE_METHOD_OFFSET = 28; |
| |
| /* |
| * the set of ImageLoader event listeners, created on demand |
| */ |
| List<ImageLoaderListener> imageLoaderListeners; |
| |
| /** |
| * Construct a new empty ImageLoader. |
| */ |
| public ImageLoader() { |
| reset(); |
| } |
| |
| /** |
| * Resets the fields of the ImageLoader, except for the |
| * <code>imageLoaderListeners</code> field. |
| */ |
| void reset() { |
| data = null; |
| logicalScreenWidth = 0; |
| logicalScreenHeight = 0; |
| backgroundPixel = -1; |
| repeatCount = 1; |
| compression = -1; |
| } |
| |
| /** |
| * Loads an array of <code>ImageData</code> objects from the |
| * specified input stream. Throws an error if either an error |
| * occurs while loading the images, or if the images are not |
| * of a supported type. Returns the loaded image data array. |
| * |
| * @param stream the input stream to load the images from |
| * @return an array of <code>ImageData</code> objects loaded from the specified input stream |
| * |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_NULL_ARGUMENT - if the stream is null</li> |
| * </ul> |
| * @exception SWTException <ul> |
| * <li>ERROR_IO - if an IO error occurs while reading from the stream</li> |
| * <li>ERROR_INVALID_IMAGE - if the image stream contains invalid data</li> |
| * <li>ERROR_UNSUPPORTED_FORMAT - if the image stream contains an unrecognized format</li> |
| * </ul> |
| */ |
| public ImageData[] load(InputStream stream) { |
| if (stream == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); |
| reset(); |
| ImageData [] imgDataArray = getImageDataArrayFromStream(stream); |
| data = imgDataArray; |
| return imgDataArray; |
| } |
| |
| /** |
| * Return true if the image is an interlaced PNG file. |
| * This is used to check whether ImageLoaderEvent should be fired when loading images. |
| * @param imageAsByteArray |
| * @return true iff 29th byte of PNG files is not zero |
| */ |
| boolean isInterlacedPNG(byte [] imageAsByteArray) { |
| return imageAsByteArray.length > PNG_INTERLACE_METHOD_OFFSET && imageAsByteArray[PNG_INTERLACE_METHOD_OFFSET] != 0; |
| } |
| |
| ImageData [] getImageDataArrayFromStream(InputStream stream) { |
| byte[] buffer = new byte[2048]; |
| long loader = GDK.gdk_pixbuf_loader_new(); |
| int length; |
| List<ImageData> imgDataList = new ArrayList<>(); |
| try { |
| // 1) Load InputStream into byte array |
| ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| while ((length = stream.read(buffer)) > -1) { |
| baos.write(buffer, 0, length); |
| } |
| baos.flush(); |
| byte[] data_buffer = baos.toByteArray(); |
| if (data_buffer.length == 0) SWT.error(SWT.ERROR_UNSUPPORTED_FORMAT); // empty stream |
| |
| // 2) Copy byte array to C memory, write to GdkPixbufLoader |
| long buffer_ptr = OS.g_malloc(data_buffer.length); |
| C.memmove(buffer_ptr, data_buffer, data_buffer.length); |
| GDK.gdk_pixbuf_loader_write(loader, buffer_ptr, data_buffer.length, null); |
| GDK.gdk_pixbuf_loader_close(loader, null); |
| |
| // 3) Get GdkPixbufAnimation from loader |
| long pixbuf_animation = GDK.gdk_pixbuf_loader_get_animation(loader); |
| if (pixbuf_animation == 0) SWT.error(SWT.ERROR_INVALID_IMAGE); |
| |
| boolean isStatic = GDK.gdk_pixbuf_animation_is_static_image(pixbuf_animation); |
| if (isStatic) { |
| // Static image, get as single pixbuf and convert it to ImageData |
| long pixbuf = GDK.gdk_pixbuf_animation_get_static_image(pixbuf_animation); |
| ImageData imgData = pixbufToImageData(pixbuf); |
| imgData.type = getImageFormat(loader); |
| imgDataList.add(imgData); |
| } else { |
| // Image with multiple frames, iterate through each frame and convert |
| // each frame to ImageData |
| long start_time = OS.g_malloc(8); |
| OS.g_get_current_time(start_time); |
| long animation_iter = GDK.gdk_pixbuf_animation_get_iter (pixbuf_animation, start_time); |
| int delay_time = 0; |
| int time_offset = 0; |
| // Fix the number of GIF frames as GdkPixbufAnimation does not provide an API to |
| // determine number of frames. |
| int num_frames = 32; |
| for (int i = 0; i < num_frames; i++) { |
| // Calculate time offset from start_time to next frame |
| delay_time = GDK.gdk_pixbuf_animation_iter_get_delay_time (animation_iter); |
| time_offset += delay_time; |
| OS.g_time_val_add(start_time, time_offset * 1000); |
| boolean update = GDK.gdk_pixbuf_animation_iter_advance (animation_iter, start_time); |
| if (update) { |
| long curr_pixbuf = GDK.gdk_pixbuf_animation_iter_get_pixbuf (animation_iter); |
| long pixbuf_copy = GDK.gdk_pixbuf_copy(curr_pixbuf); // copy because curr_pixbuf might get disposed on next advance |
| ImageData imgData = pixbufToImageData(pixbuf_copy); |
| if (this.logicalScreenHeight == 0 && this.logicalScreenWidth == 0) { |
| this.logicalScreenHeight = imgData.height; |
| this.logicalScreenWidth = imgData.width; |
| } |
| OS.g_object_unref(pixbuf_copy); |
| imgData.type = getImageFormat(loader); |
| imgData.delayTime = delay_time; |
| imgDataList.add(imgData); |
| } else { |
| break; |
| } |
| } |
| } |
| ImageData [] imgDataArray = new ImageData [imgDataList.size()]; |
| for (int i = 0; i < imgDataList.size(); i++) { |
| imgDataArray [i] = imgDataList.get(i); |
| // Loading completed, notify listeners |
| // listener should only be called when loading interlaced/progressive PNG/JPG/GIF ? |
| ImageData data = (ImageData) imgDataArray [i].clone(); |
| if (this.hasListeners() && imgDataArray != null) { |
| if (data.type == SWT.IMAGE_PNG && isInterlacedPNG(data_buffer)) { |
| this.notifyListeners(new ImageLoaderEvent(this, data, i, true)); |
| } else if (data.type != SWT.IMAGE_PNG) { |
| this.notifyListeners(new ImageLoaderEvent(this, data, i, true)); |
| } |
| } |
| } |
| OS.g_free(buffer_ptr); |
| OS.g_object_unref(loader); |
| stream.close(); |
| return imgDataArray; |
| } catch (IOException e) { |
| SWT.error(SWT.ERROR_IO); |
| } |
| return null; |
| } |
| |
| /** |
| * Loads an array of <code>ImageData</code> objects from the |
| * file with the specified name. Throws an error if either |
| * an error occurs while loading the images, or if the images are |
| * not of a supported type. Returns the loaded image data array. |
| * |
| * @param filename the name of the file to load the images from |
| * @return an array of <code>ImageData</code> objects loaded from the specified file |
| * |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_NULL_ARGUMENT - if the file name is null</li> |
| * </ul> |
| * @exception SWTException <ul> |
| * <li>ERROR_IO - if an IO error occurs while reading from the file</li> |
| * <li>ERROR_INVALID_IMAGE - if the image file contains invalid data</li> |
| * <li>ERROR_UNSUPPORTED_FORMAT - if the image file contains an unrecognized format</li> |
| * </ul> |
| */ |
| public ImageData[] load(String filename) { |
| if (filename == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); |
| InputStream stream = null; |
| try { |
| stream = new FileInputStream(filename); |
| return load(stream); |
| } catch (IOException e) { |
| SWT.error(SWT.ERROR_IO, e); |
| } finally { |
| try { |
| if (stream != null) stream.close(); |
| } catch (IOException e) { |
| // Ignore error |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Load GdkPixbuf directly using gdk_pixbuf_new_from_file, |
| * without FileInputStream. |
| * @param filename |
| * @return |
| */ |
| ImageData[] loadFromFile(String filename) { |
| long pixbuf = gdk_pixbuf_new_from_file(filename); |
| if (pixbuf == 0) return null; |
| ImageData imgData= pixbufToImageData(pixbuf); |
| return data = new ImageData[] {imgData}; |
| } |
| |
| /** |
| * Return the type of file from which the image was read |
| * by inspecting GdkPixbufFormat from GdkPixbufLoader |
| * |
| * It is expressed as one of the following values: |
| * <dl> |
| * <dt><code>IMAGE_BMP</code></dt> |
| * <dd>Windows BMP file format, no compression</dd> |
| * <dt><code>IMAGE_BMP_RLE</code></dt> |
| * <dd>Windows BMP file format, RLE compression if appropriate</dd> |
| * <dt><code>IMAGE_GIF</code></dt> |
| * <dd>GIF file format</dd> |
| * <dt><code>IMAGE_ICO</code></dt> |
| * <dd>Windows ICO file format</dd> |
| * <dt><code>IMAGE_JPEG</code></dt> |
| * <dd>JPEG file format</dd> |
| * <dt><code>IMAGE_PNG</code></dt> |
| * <dd>PNG file format</dd> |
| * </dl> |
| */ |
| int getImageFormat(long loader) { |
| long format = GDK.gdk_pixbuf_loader_get_format(loader); |
| long name = GDK.gdk_pixbuf_format_get_name(format); |
| String nameStr = Converter.cCharPtrToJavaString(name, false); |
| switch (nameStr) { |
| case "bmp": return SWT.IMAGE_BMP; |
| case "gif": return SWT.IMAGE_GIF; |
| case "ico": return SWT.IMAGE_ICO; |
| case "jpeg": return SWT.IMAGE_JPEG; |
| case "png": return SWT.IMAGE_PNG; |
| case "svg": return SWT.IMAGE_SVG; |
| default: return SWT.IMAGE_UNDEFINED; |
| } |
| } |
| |
| /** |
| * Convert GdkPixbuf pointer to Java object ImageData |
| * @param pixbuf |
| * @return ImageData with pixbuf data |
| */ |
| static ImageData pixbufToImageData(long pixbuf) { |
| boolean hasAlpha = GDK.gdk_pixbuf_get_has_alpha(pixbuf); |
| int width = GDK.gdk_pixbuf_get_width(pixbuf); |
| int height = GDK.gdk_pixbuf_get_height(pixbuf); |
| int stride = GDK.gdk_pixbuf_get_rowstride(pixbuf); |
| int n_channels = GDK.gdk_pixbuf_get_n_channels(pixbuf); // only 3 or 4 samples per pixel are supported |
| int bits_per_sample = GDK.gdk_pixbuf_get_bits_per_sample(pixbuf); // only 8 bit per sample are supported |
| long pixels = GDK.gdk_pixbuf_get_pixels(pixbuf); |
| /* |
| * From GDK Docs: last row in the pixbuf may not be as wide as the full rowstride, |
| * but rather just as wide as the pixel data needs to be. Compute the width in bytes |
| * of the last row to copy raw pixbuf data. |
| */ |
| int lastRowWidth = width * ((n_channels * bits_per_sample + 7) / 8); |
| byte[] srcData = new byte[stride * height]; |
| C.memmove(srcData, pixels, stride * (height - 1) + lastRowWidth); |
| /* |
| * Note: GdkPixbuf only supports 3/4 n_channels and 8 bits_per_sample, |
| * This means all images are of depth 24 / depth 32. This means loading |
| * images will result in a direct PaletteData with RGB masks, since |
| * there is no way to determine indexed PaletteData info. |
| * |
| * See https://www.eclipse.org/articles/Article-SWT-images/graphics-resources.html#PaletteData |
| */ |
| PaletteData palette = new PaletteData(0xFF0000, 0xFF00, 0xFF); |
| ImageData imgData = new ImageData(width, height, bits_per_sample * n_channels, palette, stride, srcData); |
| if (hasAlpha) { |
| byte[] alphaData = imgData.alphaData = new byte[width * height]; |
| for (int y = 0, offset = 0, alphaOffset = 0; y < height; y++) { |
| for (int x = 0; x < width; x++, offset += n_channels) { |
| byte r = srcData[offset + 0]; |
| byte g = srcData[offset + 1]; |
| byte b = srcData[offset + 2]; |
| byte a = srcData[offset + 3]; |
| srcData[offset + 0] = 0; |
| alphaData[alphaOffset++] = a; |
| if (a != 0) { |
| srcData[offset + 1] = r; |
| srcData[offset + 2] = g; |
| srcData[offset + 3] = b; |
| } |
| } |
| } |
| } else { |
| for (int y = 0, offset = 0; y < height; y++) { |
| for (int x = 0; x < width; x++, offset += n_channels) { |
| byte r = srcData[offset + 0]; |
| byte g = srcData[offset + 1]; |
| byte b = srcData[offset + 2]; |
| srcData[offset + 0] = r; |
| srcData[offset + 1] = g; |
| srcData[offset + 2] = b; |
| } |
| } |
| } |
| return imgData; |
| } |
| |
| /** |
| * Returns GdkPixbuf pointer by loading an image from filename (Java string) |
| * @param filename |
| * @return |
| */ |
| static long gdk_pixbuf_new_from_file(String filename) { |
| int length = filename.length (); |
| char [] chars = new char [length]; |
| filename.getChars (0, length, chars, 0); |
| byte [] buffer = Converter.wcsToMbcs(chars, true); |
| return GDK.gdk_pixbuf_new_from_file(buffer, null); |
| } |
| |
| /** |
| * Saves the image data in this ImageLoader to the specified stream. |
| * The format parameter can have one of the following values: |
| * <dl> |
| * <dt><code>IMAGE_BMP</code></dt> |
| * <dd>Windows BMP file format, no compression</dd> |
| * <dt><code>IMAGE_BMP_RLE</code></dt> |
| * <dd>Windows BMP file format, RLE compression if appropriate</dd> |
| * <dt><code>IMAGE_GIF</code></dt> |
| * <dd>GIF file format</dd> |
| * <dt><code>IMAGE_ICO</code></dt> |
| * <dd>Windows ICO file format</dd> |
| * <dt><code>IMAGE_JPEG</code></dt> |
| * <dd>JPEG file format</dd> |
| * <dt><code>IMAGE_PNG</code></dt> |
| * <dd>PNG file format</dd> |
| * </dl> |
| * |
| * @param stream the output stream to write the images to |
| * @param format the format to write the images in |
| * |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_NULL_ARGUMENT - if the stream is null</li> |
| * </ul> |
| * @exception SWTException <ul> |
| * <li>ERROR_IO - if an IO error occurs while writing to the stream</li> |
| * <li>ERROR_INVALID_IMAGE - if the image data contains invalid data</li> |
| * <li>ERROR_UNSUPPORTED_FORMAT - if the image data cannot be saved to the requested format</li> |
| * </ul> |
| */ |
| public void save(OutputStream stream, int format) { |
| if (stream == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); |
| if (format == -1) SWT.error(SWT.ERROR_UNSUPPORTED_FORMAT); |
| if (this.data == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); |
| ImageData imgData = this.data [0]; |
| int colorspace = GDK.GDK_COLORSPACE_RGB; |
| boolean alpha_supported = format == SWT.IMAGE_TIFF || format == SWT.IMAGE_PNG || format == SWT.IMAGE_ICO; |
| boolean has_alpha = imgData.alphaData != null && alpha_supported; |
| int width = imgData.width; |
| int height = imgData.height; |
| // original n_channels. Native implementation will only be used in case of 3 or 4 |
| int n_channels = imgData.bytesPerLine / width; |
| |
| // Native implementation only supports a subset of possible image configurations. |
| // Redirect the not supported variants to the Java implementation. |
| // See also https://bugs.eclipse.org/bugs/show_bug.cgi?id=558043 |
| if (!imgData.palette.isDirect || n_channels < 3 || n_channels > 4) { |
| FileFormat.save(stream, format, this); |
| return; |
| } |
| |
| /* |
| * Destination offsets, GdkPixbuf data is stored in RGBA format. |
| */ |
| int da = 3; int dr = 0; int dg = 1; int db = 2; |
| |
| /* |
| * ImageData offsets. These can vary depending on how the ImageData.data |
| * field was populated. In most cases it will be RGB format, so this case |
| * is assumed (blue shift is 0). |
| * |
| * If blue is negatively shifted, then we are dealing with BGR byte ordering, so |
| * adjust the offsets accordingly. |
| */ |
| int or = 0; int og = 1; int ob = 2; |
| PaletteData palette = imgData.palette; |
| if (palette.isDirect && palette.blueShift < 0) { |
| or = 2; |
| og = 1; |
| ob = 0; |
| } |
| |
| // We use alpha by default now so all images saved are 32 bit, if there is no alpha we set it to 255 |
| int bytes_per_pixel = 4; |
| byte[] srcData = new byte[(width * height * bytes_per_pixel)]; |
| |
| int alpha_offset = n_channels == 4 ? 1 : 0; |
| if (has_alpha) { |
| for (int y = 0, offset = 0, new_offset = 0, alphaIndex = 0; y < height; y++) { |
| for (int x = 0; x < width; x++, offset += n_channels, new_offset += bytes_per_pixel) { |
| byte a = imgData.alphaData[alphaIndex++]; |
| byte r = imgData.data[offset + alpha_offset + or]; |
| byte g = imgData.data[offset + alpha_offset + og]; |
| byte b = imgData.data[offset + alpha_offset + ob]; |
| |
| // GdkPixbuf expects RGBA format |
| srcData[new_offset + db] = b; |
| srcData[new_offset + dg] = g; |
| srcData[new_offset + dr] = r; |
| srcData[new_offset + da] = a; |
| } |
| } |
| } else { |
| for (int y = 0, offset = 0, new_offset = 0; y < height; y++) { |
| for (int x = 0; x < width; x++, offset += n_channels, new_offset += bytes_per_pixel) { |
| byte r = imgData.data[offset + alpha_offset + or]; |
| byte g = imgData.data[offset + alpha_offset + og]; |
| byte b = imgData.data[offset + alpha_offset + ob]; |
| byte a = (byte) 255; |
| |
| srcData[new_offset + db] = b; |
| srcData[new_offset + dg] = g; |
| srcData[new_offset + dr] = r; |
| srcData[new_offset + da] = a; |
| } |
| } |
| } |
| |
| // Get GdkPixbuf from pixel data buffer |
| long buffer_ptr = OS.g_malloc(srcData.length); |
| C.memmove(buffer_ptr, srcData, srcData.length); |
| int rowstride = srcData.length / height; |
| // We use alpha in all cases, if no alpha is provided then it's just 255 |
| long pixbuf = GDK.gdk_pixbuf_new_from_data (buffer_ptr, colorspace, true, 8, width, height, rowstride, 0, 0); |
| if (pixbuf == 0) { |
| OS.g_free(buffer_ptr); |
| SWT.error(SWT.ERROR_NULL_ARGUMENT); |
| } |
| |
| // Write pixbuf to byte array and then to OutputStream |
| String typeStr = ""; |
| switch (format) { |
| case SWT.IMAGE_BMP_RLE: typeStr = "bmp"; break; |
| case SWT.IMAGE_BMP: typeStr = "bmp"; break; |
| case SWT.IMAGE_GIF: typeStr = "gif"; break; |
| case SWT.IMAGE_ICO: typeStr = "ico"; break; |
| case SWT.IMAGE_JPEG: typeStr = "jpeg"; break; |
| case SWT.IMAGE_PNG: typeStr = "png"; break; |
| case SWT.IMAGE_TIFF: typeStr = "tiff"; break; |
| case SWT.IMAGE_SVG: typeStr = "svg"; break; |
| } |
| byte [] type = Converter.wcsToMbcs(typeStr, true); |
| |
| long [] buffer = new long [1]; |
| if (type == null || typeStr == "") { |
| OS.g_free(buffer_ptr); |
| SWT.error(SWT.ERROR_UNSUPPORTED_FORMAT); |
| } |
| long [] len = new long [1]; |
| GDK.gdk_pixbuf_save_to_bufferv(pixbuf, buffer, len, type, null, null, null); |
| byte[] byteArray = new byte[(int) len[0]]; |
| C.memmove(byteArray, buffer[0], byteArray.length); |
| try { |
| stream.write(byteArray); |
| } catch (IOException e) { |
| OS.g_free(buffer_ptr); |
| SWT.error(SWT.ERROR_IO); |
| } |
| // must free buffer_ptr last otherwise we get half/corrupted image |
| OS.g_free(buffer_ptr); |
| } |
| |
| /** |
| * Saves the image data in this ImageLoader to a file with the specified name. |
| * The format parameter can have one of the following values: |
| * <dl> |
| * <dt><code>IMAGE_BMP</code></dt> |
| * <dd>Windows BMP file format, no compression</dd> |
| * <dt><code>IMAGE_BMP_RLE</code></dt> |
| * <dd>Windows BMP file format, RLE compression if appropriate</dd> |
| * <dt><code>IMAGE_GIF</code></dt> |
| * <dd>GIF file format</dd> |
| * <dt><code>IMAGE_ICO</code></dt> |
| * <dd>Windows ICO file format</dd> |
| * <dt><code>IMAGE_JPEG</code></dt> |
| * <dd>JPEG file format</dd> |
| * <dt><code>IMAGE_PNG</code></dt> |
| * <dd>PNG file format</dd> |
| * </dl> |
| * |
| * @param filename the name of the file to write the images to |
| * @param format the format to write the images in |
| * |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_NULL_ARGUMENT - if the file name is null</li> |
| * </ul> |
| * @exception SWTException <ul> |
| * <li>ERROR_IO - if an IO error occurs while writing to the file</li> |
| * <li>ERROR_INVALID_IMAGE - if the image data contains invalid data</li> |
| * <li>ERROR_UNSUPPORTED_FORMAT - if the image data cannot be saved to the requested format</li> |
| * </ul> |
| */ |
| public void save(String filename, int format) { |
| if (filename == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); |
| OutputStream stream = null; |
| try { |
| stream = new FileOutputStream(filename); |
| } catch (IOException e) { |
| SWT.error(SWT.ERROR_IO, e); |
| } |
| save(stream, format); |
| try { |
| stream.close(); |
| } catch (IOException e) { |
| } |
| } |
| |
| /** |
| * Adds the listener to the collection of listeners who will be |
| * notified when image data is either partially or completely loaded. |
| * <p> |
| * An ImageLoaderListener should be added before invoking |
| * one of the receiver's load methods. The listener's |
| * <code>imageDataLoaded</code> method is called when image |
| * data has been partially loaded, as is supported by interlaced |
| * GIF/PNG or progressive JPEG images. |
| * |
| * @param listener the listener which should be notified |
| * |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> |
| * </ul> |
| * |
| * @see ImageLoaderListener |
| * @see ImageLoaderEvent |
| */ |
| public void addImageLoaderListener(ImageLoaderListener listener) { |
| if (listener == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); |
| if (imageLoaderListeners == null) { |
| imageLoaderListeners = new ArrayList<>(); |
| } |
| imageLoaderListeners.add(listener); |
| } |
| |
| /** |
| * Removes the listener from the collection of listeners who will be |
| * notified when image data is either partially or completely loaded. |
| * |
| * @param listener the listener which should no longer be notified |
| * |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> |
| * </ul> |
| * |
| * @see #addImageLoaderListener(ImageLoaderListener) |
| */ |
| public void removeImageLoaderListener(ImageLoaderListener listener) { |
| if (listener == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); |
| if (imageLoaderListeners == null) return; |
| imageLoaderListeners.remove(listener); |
| } |
| |
| /** |
| * Returns <code>true</code> if the receiver has image loader |
| * listeners, and <code>false</code> otherwise. |
| * |
| * @return <code>true</code> if there are <code>ImageLoaderListener</code>s, and <code>false</code> otherwise |
| * |
| * @see #addImageLoaderListener(ImageLoaderListener) |
| * @see #removeImageLoaderListener(ImageLoaderListener) |
| */ |
| public boolean hasListeners() { |
| return imageLoaderListeners != null && imageLoaderListeners.size() > 0; |
| } |
| |
| /** |
| * Notifies all image loader listeners that an image loader event |
| * has occurred. Pass the specified event object to each listener. |
| * |
| * @param event the <code>ImageLoaderEvent</code> to send to each <code>ImageLoaderListener</code> |
| */ |
| public void notifyListeners(ImageLoaderEvent event) { |
| if (!hasListeners()) return; |
| int size = imageLoaderListeners.size(); |
| for (int i = 0; i < size; i++) { |
| ImageLoaderListener listener = imageLoaderListeners.get(i); |
| listener.imageDataLoaded(event); |
| } |
| } |
| |
| } |