From 268b9eeac29bc721041403019144a7cdc05accb5 Mon Sep 17 00:00:00 2001 From: daRoof <35702045+daRoof@users.noreply.github.com> Date: Mon, 22 Jan 2018 19:19:05 +0100 Subject: [PATCH 1/3] 24Bit bitmap support The current implementation lacks support for bitmaps in 24-bit pixel format. This can be easily cured. --- .../graphicsio/emf/EMFImageLoader.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/freehep-graphicsio-emf/src/main/java/org/freehep/graphicsio/emf/EMFImageLoader.java b/freehep-graphicsio-emf/src/main/java/org/freehep/graphicsio/emf/EMFImageLoader.java index f5e43a18..ae79839d 100644 --- a/freehep-graphicsio-emf/src/main/java/org/freehep/graphicsio/emf/EMFImageLoader.java +++ b/freehep-graphicsio-emf/src/main/java/org/freehep/graphicsio/emf/EMFImageLoader.java @@ -239,6 +239,51 @@ else if ((bmi.getBitCount() == 16) && return result; } + else if ((bmi.getBitCount() == 24) && (bmi.getCompression() == EMFConstants.BI_RGB)) { + // Each 3 bytes in the bitmap array represents a single pixel. The + // relative intensities of red, green, and blue are represented with + // five bits for each color component. The value for red is in the first byte, + // the value for blue in the last. + // Each row of the bitmap is padded to contain a whole-number of DWORDs. + + int rowSize = 4 * ((bmi.getBitCount() * width + 31) / 32); + int[] data = emf.readUnsignedByte( len ); + + // create a non transparent image + BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + + int counter = 0; + for (int y = 0; y < height; y++) + { + for(int x = 0; x < width; x++) + { + result.setRGB(x, height - y - 1, + new Color(data[ counter++ ], data[ counter++ ], data[ counter++ ] ).getRGB()); + + if (counter >= data.length) + break; + } + + counter = (y * rowSize) + 1; + + if (counter >= data.length) + break; + } + + /* for debugging: shows every loaded image + javax.swing.JFrame f = new javax.swing.JFrame("test"); + f.getContentPane().setBackground(Color.green); + f.getContentPane().setLayout( + new java.awt.BorderLayout(0, 0)); + f.getContentPane().add( + java.awt.BorderLayout.CENTER, + new javax.swing.JLabel( + new javax.swing.ImageIcon(result))); + f.pack(); + f.setVisible(true);*/ + + return result; + } // The bitmap has a maximum of 2^32 colors. If the biCompression member of the // BITMAPINFOHEADER is BI_RGB, the bmiColors member of BITMAPINFO is NULL. else if ((bmi.getBitCount() == 32) && From b36188de81023756589db8f663e4ee0b78eeb8ad Mon Sep 17 00:00:00 2001 From: daRoof <35702045+daRoof@users.noreply.github.com> Date: Thu, 31 Jan 2019 22:04:47 +0100 Subject: [PATCH 2/3] Update EMFImageLoader.java Refactoring. Introduced methods, each one responsible for EMF images of a certain bit length. --- .../graphicsio/emf/EMFImageLoader.java | 887 +++++++++--------- 1 file changed, 464 insertions(+), 423 deletions(-) diff --git a/freehep-graphicsio-emf/src/main/java/org/freehep/graphicsio/emf/EMFImageLoader.java b/freehep-graphicsio-emf/src/main/java/org/freehep/graphicsio/emf/EMFImageLoader.java index ae79839d..7786161d 100644 --- a/freehep-graphicsio-emf/src/main/java/org/freehep/graphicsio/emf/EMFImageLoader.java +++ b/freehep-graphicsio-emf/src/main/java/org/freehep/graphicsio/emf/EMFImageLoader.java @@ -17,437 +17,478 @@ */ public class EMFImageLoader { - /** - * creates a BufferedImage from an EMFInputStream using - * BitmapInfoHeader data - * - * @param bmi BitmapInfoHeader storing Bitmap informations - * @param width expected image width - * @param height expected image height - * @param emf EMF stream - * @param len length of image data - * @param blendFunction contains values for transparency - * @return BufferedImage or null - * @throws java.io.IOException thrown by EMFInputStream - */ - public static BufferedImage readImage( - BitmapInfoHeader bmi, - int width, - int height, - EMFInputStream emf, - int len, - BlendFunction blendFunction) throws IOException { - - // 0 Windows 98/Me, Windows 2000/XP: The number of bits-per-pixel - // is specified or is implied by the JPEG or PNG format. - - if (bmi.getBitCount() == 1) { - // 1 The bitmap is monochrome, and the bmiColors - // member of BITMAPINFO contains two entries. Each - // bit in the bitmap array represents a pixel. If - // the bit is clear, the pixel is displayed with - // the color of the first entry in the bmiColors - // table; if the bit is set, the pixel has the color - // of the second entry in the table. - // byte[] bytes = emf.readByte(len); - - int blue = emf.readUnsignedByte(); - int green = emf.readUnsignedByte(); - int red = emf.readUnsignedByte(); - /*int unused =*/ emf.readUnsignedByte(); - - int color1 = new Color(red, green, blue).getRGB(); - - blue = emf.readUnsignedByte(); - green = emf.readUnsignedByte(); - red = emf.readUnsignedByte(); - /*unused = */ emf.readUnsignedByte(); - - int color2 = new Color(red, green, blue).getRGB(); - - BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); - - int[] data = emf.readUnsignedByte(len - 8); - - // TODO: this is highly experimental and does - // not work for the tested examples - int strangeOffset = width % 8; - if (strangeOffset != 0) { - strangeOffset = 8 - strangeOffset; - } - - // iterator for pixel data - int pixel = 0; - - // mask for getting the bits from a pixel data byte - int[] mask = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80}; - - // image data are swapped compared to java standard - for (int y = height - 1; y > -1; y--) { - for (int x = 0; x < width; x++) { - int pixelDataGroup = data[pixel / 8]; - int pixelData = pixelDataGroup & mask[pixel % 8]; - pixel ++; - - if (pixelData > 0) { - result.setRGB(x, y, color2); - } else { - result.setRGB(x, y, color1); - } - } - // add the extra width - pixel = pixel + strangeOffset; - } - - /* for debugging: shows every loaded image - javax.swing.JFrame f = new javax.swing.JFrame("test"); - f.getContentPane().setBackground(Color.green); - f.getContentPane().setLayout( - new java.awt.BorderLayout(0, 0)); - f.getContentPane().add( - java.awt.BorderLayout.CENTER, - new javax.swing.JLabel( - new javax.swing.ImageIcon(result))); - f.setSize(new java.awt.Dimension(width + 20, height + 20)); - f.setVisible(true);*/ - - return result; - - } else if ((bmi.getBitCount() == 8) && - (bmi.getCompression() == EMFConstants.BI_RGB)) { - // 8 The bitmap has a maximum of 256 colors, and the bmiColors member - // of BITMAPINFO contains up to 256 entries. In this case, each byte in - // the array represents a single pixel. - - // TODO has to be done in BitMapInfoHeader? - // read the color table - int colorsUsed = bmi.getClrUsed(); - - // typedef struct tagRGBQUAD { - // BYTE rgbBlue; - // BYTE rgbGreen; - // BYTE rgbRed; - // BYTE rgbReserved; - // } RGBQUAD; - int[] colors = emf.readUnsignedByte(colorsUsed * 4); - - // data a indexes to a certain color in the colortable. - // Each byte represents a pixel - int[] data = emf.readUnsignedByte(len - (colorsUsed * 4)); - - // convert it to a color table - int[] colorTable = new int[256]; - // iterator for color data - int color = 0; - for (int i = 0; i < colorsUsed; i++, color = i * 4) { - colorTable[i] = new Color( - colors[color + 2], - colors[color + 1], - colors[color]).getRGB(); - } - - // fill with black to avoid ArrayIndexOutOfBoundExceptions; - // somme images seem to use more colors than stored in ClrUsed - if (colorsUsed < 256) { - Arrays.fill(colorTable, colorsUsed, 256, 0); + /** + * creates a BufferedImage from an EMFInputStream using + * BitmapInfoHeader data + * + * @param bmi BitmapInfoHeader storing Bitmap informations + * @param width expected image width + * @param height expected image height + * @param emf EMF stream + * @param len length of image data + * @param blendFunction contains values for transparency + * @return BufferedImage or null + * @throws java.io.IOException thrown by EMFInputStream + */ + public static BufferedImage readImage( BitmapInfoHeader bmi, int width, int height, EMFInputStream emf, + int len, BlendFunction blendFunction ) throws IOException + { + // 0 Windows 98/Me, Windows 2000/XP: The number of bits-per-pixel + // is specified or is implied by the JPEG or PNG format. + + if( bmi.getBitCount() == 1 ) + { + return handle1Bit( emf, width, height, len ); + } + else if( ( bmi.getBitCount() == 8 ) && ( bmi.getCompression() == EMFConstants.BI_RGB ) ) + { + return handle8Bit_RGB( emf, width, height, len, bmi ); + } + + // The bitmap has a maximum of 2^16 colors. If the biCompression member + // of the BITMAPINFOHEADER is BI_RGB, the bmiColors member of BITMAPINFO is + // NULL. + else if( ( bmi.getBitCount() == 16 ) && ( bmi.getCompression() == EMFConstants.BI_RGB ) ) + { + return handle16Bit_RGB( emf, width, height, len ); + } + else if( ( bmi.getBitCount() == 24 ) && ( bmi.getCompression() == EMFConstants.BI_RGB ) ) + { + return handle24Bit_RGB( emf, width, height, len, bmi ); + } + // The bitmap has a maximum of 2^32 colors. If the biCompression member of the + // BITMAPINFOHEADER is BI_RGB, the bmiColors member of BITMAPINFO is NULL. + else if( ( bmi.getBitCount() == 32 ) && ( bmi.getCompression() == EMFConstants.BI_RGB ) ) + { + return handle32Bit_RGB( emf, width, height, len, blendFunction ); + } + // If the biCompression member of the BITMAPINFOHEADER is BI_BITFIELDS, + // the bmiColors member contains three DWORD color masks that specify the + // red, green, and blue components, respectively, of each pixel. Each DWORD + // in the bitmap array represents a single pixel. + + // Windows NT/ 2000: When the biCompression member is BI_BITFIELDS, bits set in + // each DWORD mask must be contiguous and should not overlap the bits of + // another mask. All the bits in the pixel do not need to be used. + + // Windows 95/98/Me: When the biCompression member is BI_BITFIELDS, the system + // supports only the following 32-bpp color mask: The blue mask is 0x000000FF, + // the green mask is 0x0000FF00, and the red mask is 0x00FF0000. + else if( ( bmi.getBitCount() == 32 ) && ( bmi.getCompression() == EMFConstants.BI_BITFIELDS ) ) + { + /* byte[] bytes =*/ emf.readByte( len ); + return null; + } + else + { + /* byte[] bytes =*/ emf.readByte( len ); + return null; + } + } + + protected static BufferedImage handle1Bit( EMFInputStream emf, int width, int height, int len ) + throws IOException + { + // 1 The bitmap is monochrome, and the bmiColors + // member of BITMAPINFO contains two entries. Each + // bit in the bitmap array represents a pixel. If + // the bit is clear, the pixel is displayed with + // the color of the first entry in the bmiColors + // table; if the bit is set, the pixel has the color + // of the second entry in the table. + // byte[] bytes = emf.readByte(len); + + int blue = emf.readUnsignedByte(); + int green = emf.readUnsignedByte(); + int red = emf.readUnsignedByte(); + /*int unused =*/ emf.readUnsignedByte(); + + int color1 = new Color( red, green, blue ).getRGB(); + + blue = emf.readUnsignedByte(); + green = emf.readUnsignedByte(); + red = emf.readUnsignedByte(); + /*unused = */ emf.readUnsignedByte(); + + int color2 = new Color( red, green, blue ).getRGB(); + + BufferedImage result = new BufferedImage( width, height, BufferedImage.TYPE_INT_RGB ); + + int[] data = emf.readUnsignedByte( len - 8 ); + + // TODO: this is highly experimental and does + // not work for the tested examples + int strangeOffset = width % 8; + if( strangeOffset != 0 ) + { + strangeOffset = 8 - strangeOffset; + } + + // iterator for pixel data + int pixel = 0; + + // mask for getting the bits from a pixel data byte + int[] mask = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 }; + + // image data are swapped compared to java standard + for( int y = height - 1; y > -1; y-- ) + { + for( int x = 0; x < width; x++ ) + { + int pixelDataGroup = data[ pixel / 8 ]; + int pixelData = pixelDataGroup & mask[ pixel % 8 ]; + pixel++; + + if( pixelData > 0 ) + { + result.setRGB( x, y, color2 ); } - - // don't know why, but the width has to be adjusted ... - // it took more than an hour to determine the strangeOffset - int strangeOffset = width % 4; - if (strangeOffset != 0) { - strangeOffset = 4 - strangeOffset; + else + { + result.setRGB( x, y, color1 ); } + } + // add the extra width + pixel = pixel + strangeOffset; + } + + /* for debugging: shows every loaded image + javax.swing.JFrame f = new javax.swing.JFrame("test"); + f.getContentPane().setBackground(Color.green); + f.getContentPane().setLayout( + new java.awt.BorderLayout(0, 0)); + f.getContentPane().add( + java.awt.BorderLayout.CENTER, + new javax.swing.JLabel( + new javax.swing.ImageIcon(result))); + f.setSize(new java.awt.Dimension(width + 20, height + 20)); + f.setVisible(true);*/ + + return result; + } + + protected static BufferedImage handle8Bit_RGB( EMFInputStream emf, int width, int height, int len, + BitmapInfoHeader bmi ) throws IOException + { + // 8 The bitmap has a maximum of 256 colors, and the bmiColors member + // of BITMAPINFO contains up to 256 entries. In this case, each byte in + // the array represents a single pixel. + + // TODO has to be done in BitMapInfoHeader? + // read the color table + int colorsUsed = bmi.getClrUsed(); + + // typedef struct tagRGBQUAD { + // BYTE rgbBlue; + // BYTE rgbGreen; + // BYTE rgbRed; + // BYTE rgbReserved; + // } RGBQUAD; + int[] colors = emf.readUnsignedByte( colorsUsed * 4 ); + + // data a indexes to a certain color in the color table. + // Each byte represents a pixel + int[] data = emf.readUnsignedByte( len - ( colorsUsed * 4 ) ); + + // convert it to a color table + int[] colorTable = new int[ 256 ]; + // iterator for color data + int color = 0; + for( int i = 0; i < colorsUsed; i++, color = i * 4 ) + { + colorTable[ i ] = new Color( colors[ color + 2 ], colors[ color + 1 ], colors[ color ] ).getRGB(); + } + + // fill with black to avoid ArrayIndexOutOfBoundExceptions; + // some images seem to use more colors than stored in ClrUsed + if( colorsUsed < 256 ) + { + Arrays.fill( colorTable, colorsUsed, 256, 0 ); + } + + // don't know why, but the width has to be adjusted ... + // it took more than an hour to determine the strangeOffset + int strangeOffset = width % 4; + if( strangeOffset != 0 ) + { + strangeOffset = 4 - strangeOffset; + } + + // create the image + BufferedImage result = new BufferedImage( width, height, BufferedImage.TYPE_INT_RGB ); + + // iterator for pixel data + int pixel = 0; + + // image data are swapped compared to java standard + for( int y = height - 1; y > -1; y-- ) + { + for( int x = 0; x < width; x++ ) + { + result.setRGB( x, y, colorTable[ data[ pixel++ ] ] ); + } + // add the extra width + pixel = pixel + strangeOffset; + } + + return result; + } + + protected static BufferedImage handle16Bit_RGB( EMFInputStream emf, int width, int height, int len ) + throws IOException + { + // Each WORD in the bitmap array represents a single pixel. The + // relative intensities of red, green, and blue are represented with + // five bits for each color component. The value for blue is in the least + // significant five bits, followed by five bits each for green and red. + // The most significant bit is not used. The bmiColors color table is used + // for optimizing colors used on palette-based devices, and must contain + // the number of entries specified by the biClrUsed member of the + // BITMAPINFOHEADER. + int[] data = emf.readDWORD( len / 4 ); + + // don't know why, by the width has to be the half ... + // maybe that has something to do with the HALFTONE rendering setting. + width = ( width + ( width % 2 ) ) / 2; + // to avoid ArrayIndexOutOfBoundExcesptions + height = data.length / width / 2; + + // create a non transparent image + BufferedImage result = new BufferedImage( width, height, BufferedImage.TYPE_INT_RGB ); + + // found no sample and color model to make this work + // tag.image.setRGB(0, 0, tag.widthSrc, tag.heightSrc, data, 0, 0); + + // used in the loop + int off = 0; + int pixel, neighbor; + + // image data are swapped compared to java standard + for( int y = height - 1; y > -1; y--, off = off + width ) + { + for( int x = 0; x < width; x++ ) + { + neighbor = data[ off + width ]; + pixel = data[ off++ ]; + + // compute the average of the pixel and it's neighbor + // and set the resulting color values + result.setRGB( x, y, + new Color( + // 0xF800 = 2 * 0x7C00 + ( float ) ( ( pixel & 0x7C00 ) + ( neighbor & 0x7C00 ) ) / 0xF800, + ( float ) ( ( pixel & 0x3E0 ) + ( neighbor & 0x3E0 ) ) / 0x7C0, + ( float ) ( ( pixel & 0x1F ) + ( neighbor & 0x1F ) ) / 0x3E ).getRGB() ); + } + } + + /* for debugging: shows every loaded image + javax.swing.JFrame f = new javax.swing.JFrame("test"); + f.getContentPane().setBackground(Color.green); + f.getContentPane().setLayout( + new java.awt.BorderLayout(0, 0)); + f.getContentPane().add( + java.awt.BorderLayout.CENTER, + new javax.swing.JLabel( + new javax.swing.ImageIcon(result))); + f.pack(); + f.setVisible(true);*/ + + return result; + } + + protected static BufferedImage handle24Bit_RGB( EMFInputStream emf, int width, int height, int len, + BitmapInfoHeader bmi ) throws IOException + { + // Each 3 bytes in the bitmap array represents a single pixel. The + // relative intensities of red, green, and blue are represented with + // five bits for each color component. The value for red is in the first byte, + // the value for blue in the last. + // Each row of the bitmap is padded to contain a whole-number of DWORDs. + + int rowSize = 4 * ( ( bmi.getBitCount() * width + 31 ) / 32 ); + int[] data = emf.readUnsignedByte( len ); + + // create a non transparent image + BufferedImage result = new BufferedImage( width, height, BufferedImage.TYPE_INT_RGB ); + + int counter = 0; + for( int y = 0; y < height; y++ ) + { + for( int x = 0; x < width; x++ ) + { + result.setRGB( x, height - y - 1, + new Color( data[ counter++ ], data[ counter++ ], data[ counter++ ] ).getRGB() ); + + if( counter >= data.length ) + break; + } + + counter = ( y * rowSize ) + 1; + + if( counter >= data.length ) + break; + } + + /* for debugging: shows every loaded image + javax.swing.JFrame f = new javax.swing.JFrame("test"); + f.getContentPane().setBackground(Color.green); + f.getContentPane().setLayout( + new java.awt.BorderLayout(0, 0)); + f.getContentPane().add( + java.awt.BorderLayout.CENTER, + new javax.swing.JLabel( + new javax.swing.ImageIcon(result))); + f.pack(); + f.setVisible(true);*/ + + return result; + } + + protected static BufferedImage handle32Bit_RGB( EMFInputStream emf, int width, int height, int len, + BlendFunction blendFunction ) throws IOException + { + // Each DWORD in the bitmap array represents the relative intensities of blue, + // green, and red, respectively, for a pixel. The high byte in each DWORD is not + // used. The bmiColors color table is used for optimizing colors used on + // palette-based devices, and must contain the number of entries specified + // by the biClrUsed member of the BITMAPINFOHEADER. + + width = ( width + ( width % 20 ) ) / 20; + height = ( height + ( height % 20 ) ) / 20; + + // create a transparent image + BufferedImage result = new BufferedImage( width, height, BufferedImage.TYPE_INT_ARGB ); + // read the image data + int[] data = emf.readDWORD( len / 4 ); + + // used to iterate the pixels later + int off = 0; + int pixel; + int alpha; + + // The SourceConstantaAlpha member of BLENDFUNCTION specifies an alpha transparency + // value to be used on the entire source bitmap. The SourceConstantAlpha value is + // combined with any per-pixel alpha values. If SourceConstantAlpha is 0, it is + // assumed that the image is transparent. Set the SourceConstantAlpha value to 255 + // (which indicates that the image is opaque) when you only want to use per-pixel + // alpha values. + int sourceConstantAlpha = blendFunction.getSourceConstantAlpha(); + + if( blendFunction.getAlphaFormat() != EMFConstants.AC_SRC_ALPHA ) + { + // If the source bitmap has no per-pixel alpha value (that is, AC_SRC_ALPHA is not + // set), the SourceConstantAlpha value determines the blend of the source and + // destination bitmaps, as shown in the following table. Note that SCA is used + // for SourceConstantAlpha here. Also, SCA is divided by 255 because it has a + // value that ranges from 0 to 255. + + // Dst.Red = Src.Red * (SCA/255.0) + Dst.Red * (1.0 - (SCA/255.0)) + // Dst.Green = Src.Green * (SCA/255.0) + Dst.Green * (1.0 - (SCA/255.0)) + // Dst.Blue = Src.Blue * (SCA/255.0) + Dst.Blue * (1.0 - (SCA/255.0)) + + // If the destination bitmap has an alpha channel, then the blend is as follows. + // Dst.Alpha = Src.Alpha * (SCA/255.0) + Dst.Alpha * (1.0 - (SCA/255.0)) + + for( int y = height - 1; y > -1 && off < data.length; y-- ) + { + for( int x = 0; x < width && off < data.length; x++ ) + { + pixel = data[ off++ ]; - // create the image - BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); - - // iterator for pixel data - int pixel = 0; - - // image data are swapped compared to java standard - for (int y = height - 1; y > -1; y--) { - for (int x = 0; x < width; x++) { - result.setRGB(x, y, colorTable[data[pixel++]]); - } - // add the extra width - pixel = pixel + strangeOffset; + result.setRGB( x, y, + new Color( ( pixel & 0xFF0000 ) >> 16, ( pixel & 0xFF00 ) >> 8, ( pixel & 0xFF ), + // TODO not tested + sourceConstantAlpha ).getRGB() ); } - - return result; - } - - // The bitmap has a maximum of 2^16 colors. If the biCompression member - // of the BITMAPINFOHEADER is BI_RGB, the bmiColors member of BITMAPINFO is - // NULL. - else if ((bmi.getBitCount() == 16) && - (bmi.getCompression() == EMFConstants.BI_RGB)) { - - // Each WORD in the bitmap array represents a single pixel. The - // relative intensities of red, green, and blue are represented with - // five bits for each color component. The value for blue is in the least - // significant five bits, followed by five bits each for green and red. - // The most significant bit is not used. The bmiColors color table is used - // for optimizing colors used on palette-based devices, and must contain - // the number of entries specified by the biClrUsed member of the - // BITMAPINFOHEADER. - int[] data = emf.readDWORD(len / 4); - - // don't know why, by the width has to be the half ... - // maybe that has something to do with sie HALFTONE rendering setting. - width = (width + (width % 2)) / 2; - // to avoid ArrayIndexOutOfBoundExcesptions - height = data.length / width / 2; - - // create a non transparent image - BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); - - // found no sample and color model to mak this work - // tag.image.setRGB(0, 0, tag.widthSrc, tag.heightSrc, data, 0, 0); - - // used in the loop - int off = 0; - int pixel, neighbor; + } + } + // When the BlendOp parameter is AC_SRC_OVER , the source bitmap is placed over + // the destination bitmap based on the alpha values of the source pixels. + else + { + // If the source bitmap does not use SourceConstantAlpha (that is, it equals + // 0xFF), the per-pixel alpha determines the blend of the source and destination + // bitmaps, as shown in the following table. + if( sourceConstantAlpha == 0xFF ) + { + // Dst.Red = Src.Red + (1 - Src.Alpha) * Dst.Red + // Dst.Green = Src.Green + (1 - Src.Alpha) * Dst.Green + // Dst.Blue = Src.Blue + (1 - Src.Alpha) * Dst.Blue + + // If the destination bitmap has an alpha channel, then the blend is as follows. + // Dest.alpha = Src.Alpha + (1 - SrcAlpha) * Dst.Alpha // image data are swapped compared to java standard - for (int y = height - 1; y > -1; y--, off = off + width) { - for (int x = 0; x < width; x++) { - neighbor = data[off + width]; - pixel = data[off++]; - - // compute the average of the pixel and it's neighbor - // and set the reulting color values - result.setRGB(x, y, new Color( - // 0xF800 = 2 * 0x7C00 - (float)((pixel & 0x7C00) + (neighbor & 0x7C00)) / 0xF800, - (float)((pixel & 0x3E0) + (neighbor & 0x3E0)) / 0x7C0, - (float)((pixel & 0x1F) + (neighbor & 0x1F)) / 0x3E).getRGB()); - } + for( int y = height - 1; y > -1 && off < data.length; y-- ) + { + for( int x = 0; x < width && off < data.length; x++ ) + { + pixel = data[ off++ ]; + alpha = ( pixel & 0xFF000000 ) >> 24; + if( alpha == -1 ) + { + alpha = 0xFF; + } + + result.setRGB( x, y, new Color( ( pixel & 0xFF0000 ) >> 16, ( pixel & 0xFF00 ) >> 8, + ( pixel & 0xFF ), alpha ).getRGB() ); + } } - - /* for debugging: shows every loaded image - javax.swing.JFrame f = new javax.swing.JFrame("test"); - f.getContentPane().setBackground(Color.green); - f.getContentPane().setLayout( - new java.awt.BorderLayout(0, 0)); - f.getContentPane().add( - java.awt.BorderLayout.CENTER, - new javax.swing.JLabel( - new javax.swing.ImageIcon(result))); - f.pack(); - f.setVisible(true);*/ - - return result; - } - else if ((bmi.getBitCount() == 24) && (bmi.getCompression() == EMFConstants.BI_RGB)) { - // Each 3 bytes in the bitmap array represents a single pixel. The - // relative intensities of red, green, and blue are represented with - // five bits for each color component. The value for red is in the first byte, - // the value for blue in the last. - // Each row of the bitmap is padded to contain a whole-number of DWORDs. - - int rowSize = 4 * ((bmi.getBitCount() * width + 31) / 32); - int[] data = emf.readUnsignedByte( len ); - - // create a non transparent image - BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); - - int counter = 0; - for (int y = 0; y < height; y++) + } + + // If the source has both the SourceConstantAlpha (that is, it is not 0xFF) + // and per-pixel alpha, the source is pre-multiplied by the SourceConstantAlpha + // and then the blend is based on the per-pixel alpha. The following tables show + // this. Note that SourceConstantAlpha is divided by 255 because it has a value + // that ranges from 0 to 255. + else + { + // Src.Red = Src.Red * SourceConstantAlpha / 255.0; + // Src.Green = Src.Green * SourceConstantAlpha / 255.0; + // Src.Blue = Src.Blue * SourceConstantAlpha / 255.0; + // Src.Alpha = Src.Alpha * SourceConstantAlpha / 255.0; + + // Dst.Red = Src.Red + (1 - Src.Alpha) * Dst.Red + // Dst.Green = Src.Green + (1 - Src.Alpha) * Dst.Green + // Dst.Blue = Src.Blue + (1 - Src.Alpha) * Dst.Blue + // Dst.Alpha = Src.Alpha + (1 - Src.Alpha) * Dst.Alpha + + for( int y = height - 1; y > -1 && off < data.length; y-- ) { - for(int x = 0; x < width; x++) - { - result.setRGB(x, height - y - 1, - new Color(data[ counter++ ], data[ counter++ ], data[ counter++ ] ).getRGB()); - - if (counter >= data.length) - break; - } - - counter = (y * rowSize) + 1; - - if (counter >= data.length) - break; + for( int x = 0; x < width && off < data.length; x++ ) + { + pixel = data[ off++ ]; + + alpha = ( pixel & 0xFF000000 ) >> 24; + if( alpha == -1 ) + { + alpha = 0xFF; + } + + // TODO not tested + alpha = alpha * sourceConstantAlpha / 0xFF; + + result.setRGB( x, y, new Color( ( pixel & 0xFF0000 ) >> 16, ( pixel & 0xFF00 ) >> 8, + ( pixel & 0xFF ), alpha ).getRGB() ); + } } + } + } + + /* for debugging: shows every loaded image + javax.swing.JFrame f = new javax.swing.JFrame("test"); + f.getContentPane().setBackground(Color.green); + f.getContentPane().setLayout( + new java.awt.BorderLayout(0, 0)); + f.getContentPane().add( + java.awt.BorderLayout.CENTER, + new javax.swing.JLabel( + new javax.swing.ImageIcon(result))); + f.setSize(new java.awt.Dimension(width + 20, height + 20)); + f.setVisible(true);*/ + + return result; + } - /* for debugging: shows every loaded image - javax.swing.JFrame f = new javax.swing.JFrame("test"); - f.getContentPane().setBackground(Color.green); - f.getContentPane().setLayout( - new java.awt.BorderLayout(0, 0)); - f.getContentPane().add( - java.awt.BorderLayout.CENTER, - new javax.swing.JLabel( - new javax.swing.ImageIcon(result))); - f.pack(); - f.setVisible(true);*/ - - return result; - } - // The bitmap has a maximum of 2^32 colors. If the biCompression member of the - // BITMAPINFOHEADER is BI_RGB, the bmiColors member of BITMAPINFO is NULL. - else if ((bmi.getBitCount() == 32) && - (bmi.getCompression() == EMFConstants.BI_RGB)) { - // Each DWORD in the bitmap array represents the relative intensities of blue, - // green, and red, respectively, for a pixel. The high byte in each DWORD is not - // used. The bmiColors color table is used for optimizing colors used on - // palette-based devices, and must contain the number of entries specified - // by the biClrUsed member of the BITMAPINFOHEADER. - - width = (width + (width % 20)) / 20; - height = (height + (height % 20)) / 20; - - // create a transparent image - BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); - // read the image data - int[] data = emf.readDWORD(len / 4); - - // used to iterate the pixels later - int off = 0; - int pixel; - int alpha; - - // The SourceConstantaAlpha member of BLENDFUNCTION specifies an alpha transparency - // value to be used on the entire source bitmap. The SourceConstantAlpha value is - // combined with any per-pixel alpha values. If SourceConstantAlpha is 0, it is - // assumed that the image is transparent. Set the SourceConstantAlpha value to 255 - // (which indicates that the image is opaque) when you only want to use per-pixel - // alpha values. - int sourceConstantAlpha = blendFunction.getSourceConstantAlpha(); - - if (blendFunction.getAlphaFormat() != EMFConstants.AC_SRC_ALPHA) { - // If the source bitmap has no per-pixel alpha value (that is, AC_SRC_ALPHA is not - // set), the SourceConstantAlpha value determines the blend of the source and - // destination bitmaps, as shown in the following table. Note that SCA is used - // for SourceConstantAlpha here. Also, SCA is divided by 255 because it has a - // value that ranges from 0 to 255. - - // Dst.Red = Src.Red * (SCA/255.0) + Dst.Red * (1.0 - (SCA/255.0)) - // Dst.Green = Src.Green * (SCA/255.0) + Dst.Green * (1.0 - (SCA/255.0)) - // Dst.Blue = Src.Blue * (SCA/255.0) + Dst.Blue * (1.0 - (SCA/255.0)) - - // If the destination bitmap has an alpha channel, then the blend is as follows. - // Dst.Alpha = Src.Alpha * (SCA/255.0) + Dst.Alpha * (1.0 - (SCA/255.0)) - - for (int y = height - 1; y > -1 && off < data.length; y--) { - for (int x = 0; x < width && off < data.length; x++) { - pixel = data[off++]; - - result.setRGB(x, y, new Color( - (pixel & 0xFF0000) >> 16, - (pixel & 0xFF00) >> 8, - (pixel & 0xFF), - // TODO not tested - sourceConstantAlpha - ).getRGB()); - } - } - } - // When the BlendOp parameter is AC_SRC_OVER , the source bitmap is placed over - // the destination bitmap based on the alpha values of the source pixels. - else { - // If the source bitmap does not use SourceConstantAlpha (that is, it equals - // 0xFF), the per-pixel alpha determines the blend of the source and destination - // bitmaps, as shown in the following table. - if (sourceConstantAlpha == 0xFF) { - // Dst.Red = Src.Red + (1 - Src.Alpha) * Dst.Red - // Dst.Green = Src.Green + (1 - Src.Alpha) * Dst.Green - // Dst.Blue = Src.Blue + (1 - Src.Alpha) * Dst.Blue - - // If the destination bitmap has an alpha channel, then the blend is as follows. - // Dest.alpha = Src.Alpha + (1 - SrcAlpha) * Dst.Alpha - - // image data are swapped compared to java standard - for (int y = height - 1; y > -1 && off < data.length; y--) { - for (int x = 0; x < width && off < data.length; x++) { - pixel = data[off++]; - alpha = (pixel & 0xFF000000) >> 24; - if (alpha == -1) { - alpha = 0xFF; - } - - result.setRGB(x, y, new Color( - (pixel & 0xFF0000) >> 16, - (pixel & 0xFF00) >> 8, - (pixel & 0xFF), - alpha - ).getRGB()); - } - } - } - - // If the source has both the SourceConstantAlpha (that is, it is not 0xFF) - // and per-pixel alpha, the source is pre-multiplied by the SourceConstantAlpha - // and then the blend is based on the per-pixel alpha. The following tables show - // this. Note that SourceConstantAlpha is divided by 255 because it has a value - // that ranges from 0 to 255. - else { - // Src.Red = Src.Red * SourceConstantAlpha / 255.0; - // Src.Green = Src.Green * SourceConstantAlpha / 255.0; - // Src.Blue = Src.Blue * SourceConstantAlpha / 255.0; - // Src.Alpha = Src.Alpha * SourceConstantAlpha / 255.0; - - // Dst.Red = Src.Red + (1 - Src.Alpha) * Dst.Red - // Dst.Green = Src.Green + (1 - Src.Alpha) * Dst.Green - // Dst.Blue = Src.Blue + (1 - Src.Alpha) * Dst.Blue - // Dst.Alpha = Src.Alpha + (1 - Src.Alpha) * Dst.Alpha - - for (int y = height - 1; y > -1 && off < data.length; y--) { - for (int x = 0; x < width && off < data.length; x++) { - pixel = data[off++]; - - alpha = (pixel & 0xFF000000) >> 24; - if (alpha == -1) { - alpha = 0xFF; - } - - // TODO not tested - alpha = alpha * sourceConstantAlpha / 0xFF; - - result.setRGB(x, y, new Color( - (pixel & 0xFF0000) >> 16, - (pixel & 0xFF00) >> 8, - (pixel & 0xFF), - alpha - ).getRGB()); - } - } - } - } - - /* for debugging: shows every loaded image - javax.swing.JFrame f = new javax.swing.JFrame("test"); - f.getContentPane().setBackground(Color.green); - f.getContentPane().setLayout( - new java.awt.BorderLayout(0, 0)); - f.getContentPane().add( - java.awt.BorderLayout.CENTER, - new javax.swing.JLabel( - new javax.swing.ImageIcon(result))); - f.setSize(new java.awt.Dimension(width + 20, height + 20)); - f.setVisible(true);*/ - - return result; - } - // If the biCompression member of the BITMAPINFOHEADER is BI_BITFIELDS, - // the bmiColors member contains three DWORD color masks that specify the - // red, green, and blue components, respectively, of each pixel. Each DWORD - // in the bitmap array represents a single pixel. - - // Windows NT/ 2000: When the biCompression member is BI_BITFIELDS, bits set in - // each DWORD mask must be contiguous and should not overlap the bits of - // another mask. All the bits in the pixel do not need to be used. - - // Windows 95/98/Me: When the biCompression member is BI_BITFIELDS, the system - // supports only the following 32-bpp color mask: The blue mask is 0x000000FF, - // the green mask is 0x0000FF00, and the red mask is 0x00FF0000. - else if ((bmi.getBitCount() == 32) && - (bmi.getCompression() == EMFConstants.BI_BITFIELDS)) { - /* byte[] bytes =*/ emf.readByte(len); - return null; - } else { - /* byte[] bytes =*/ emf.readByte(len); - return null; - } - } } From 2602d24224f076ce5e58171d8e5d3993f9c121fb Mon Sep 17 00:00:00 2001 From: daRoof <35702045+daRoof@users.noreply.github.com> Date: Fri, 1 Feb 2019 00:10:05 +0100 Subject: [PATCH 3/3] Possible NullPointerException The EMFImageLoader is called multiple times without providing a BlendFunction. see #43 This causes a NPE, in case the image is a 32 bit EMF. I don't know if 0xFF is the correct default value, but at least this fixes the NPE. --- .../java/org/freehep/graphicsio/emf/EMFImageLoader.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/freehep-graphicsio-emf/src/main/java/org/freehep/graphicsio/emf/EMFImageLoader.java b/freehep-graphicsio-emf/src/main/java/org/freehep/graphicsio/emf/EMFImageLoader.java index 7786161d..b7cc586e 100644 --- a/freehep-graphicsio-emf/src/main/java/org/freehep/graphicsio/emf/EMFImageLoader.java +++ b/freehep-graphicsio-emf/src/main/java/org/freehep/graphicsio/emf/EMFImageLoader.java @@ -366,16 +366,19 @@ protected static BufferedImage handle32Bit_RGB( EMFInputStream emf, int width, i int off = 0; int pixel; int alpha; - + int sourceConstantAlpha = 0xFF; // assume default value + // The SourceConstantaAlpha member of BLENDFUNCTION specifies an alpha transparency // value to be used on the entire source bitmap. The SourceConstantAlpha value is // combined with any per-pixel alpha values. If SourceConstantAlpha is 0, it is // assumed that the image is transparent. Set the SourceConstantAlpha value to 255 // (which indicates that the image is opaque) when you only want to use per-pixel // alpha values. - int sourceConstantAlpha = blendFunction.getSourceConstantAlpha(); + if( null != blendFunction ) { + sourceConstantAlpha = blendFunction.getSourceConstantAlpha(); + } - if( blendFunction.getAlphaFormat() != EMFConstants.AC_SRC_ALPHA ) + if( null != blendFunction && blendFunction.getAlphaFormat() != EMFConstants.AC_SRC_ALPHA ) { // If the source bitmap has no per-pixel alpha value (that is, AC_SRC_ALPHA is not // set), the SourceConstantAlpha value determines the blend of the source and