I need to create tiled pyramid TIFF file with JPEG-compressed tiles from large JPEG image using lossless jpeg operations i.e. crop in jpegtran. Is there any tool or java code around to perform such operation?
Currently I'm using my Java code to create tiled pyramid TIFF, but it doing it in lossy way:
public static void writeTiff(RenderedOp src, File dstFile, float jpegQuality, int tileSize, int pyramid, String interpolation) throws IOException {
dstFile.getParentFile().mkdirs();
TIFFImageWriterSpi imageWriterSpi = new TIFFImageWriterSpi();
TIFFImageWriter imageWriter = (TIFFImageWriter)imageWriterSpi.createWriterInstance();
ImageOutputStream out = new FileImageOutputStream(dstFile);
try {
imageWriter.setOutput(out);
imageWriter.prepareWriteSequence(null);
BufferedImage img = null;
for(int i=0; i<pyramid; i++) {
img = img==null ? src.getAsBufferedImage() : JAIUtils.scale(img, 0.5F, interpolation).getAsBufferedImage();
TIFFImageWriteParam imageWriteParam = (TIFFImageWriteParam)imageWriter.getDefaultWriteParam();
if (tileSize>0 && (img.getWidth()>tileSize || img.getHeight()>tileSize)) {
imageWriteParam.setTilingMode(ImageWriteParam.MODE_EXPLICIT);
imageWriteParam.setTiling(tileSize, tileSize, 0, 0);
}
if (jpegQuality > 0) {
imageWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT );
imageWriteParam.setCompressionType("JPEG");
imageWriteParam.setCompressionQuality(jpegQuality);
}
imageWriter.writeToSequence(new IIOImage(img, null, null), imageWriteParam );
}
imageWriter.endWriteSequence();
} finally {
out.close();
}
}
Ossim works well from the command line, but I see it has a JNI binding, too.
Related
Using Java ImageIO, is it possible to export a jpeg image that has a bit-depth of 8? How would I do this? Even when exporting a BufferedImage of TYPE_BYTE_BINARY, which is a grayscale image, the result is a JPEG with bit-depth of 24.
This is what I have so far.
public void testJpegBitDepth() throws Exception{
Path pIn = Paths.get("testing/jpg/box1.jpg"), pOut;
BufferedImage bi;
//*******************************************
//Write 8 bit jpg
//Init ImageWriter
Iterator<ImageWriter> it = ImageIO.getImageWritersByFormatName("jpg");
ImageWriter writer = null;
while(it.hasNext()) {
try {
writer = it.next();
//Read input
bi = ImageIO.read(pIn.toFile());
if(bi == null)
throw new Exception("Failed to read input file: " + pIn);
//Convert to gray
bi = AWTImaging.convertToGray(bi);
log.debug("Num bands from the image raster: " + bi.getRaster().getNumBands());
pOut = test.outputDir.resolve("jpegBitDepth-8-"
+ pIn.getFileName().toString() + ".jpg");
//Init ImageTypeSpecifier
ImageTypeSpecifier imageType = ImageTypeSpecifier.createGrayscale(
8, //8 bits per pixel
DataBuffer.TYPE_BYTE, //stored in a byte
false); //unsigned
//Init WriteParam
ImageWriteParam param = writer.getDefaultWriteParam();
param.setDestinationType(imageType);
//Not sure if this is required or not, but the same Exception occurs either way
//param.setSourceBands(new int[] {0});
//Init meta
IIOMetadata meta = writer.getDefaultImageMetadata(imageType, param);
String metadataFormat = "javax_imageio_jpeg_image_1.0";
IIOMetadataNode root = new IIOMetadataNode(metadataFormat);
IIOMetadataNode jpegVariety = new IIOMetadataNode("JPEGvariety");
IIOMetadataNode markerSequence = new IIOMetadataNode("markerSequence");
//I think we want app0JFIF metadata here, as it can specify a grayscale image https://docs.oracle.com/javase/10/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html
IIOMetadataNode app0JFIF = new IIOMetadataNode("app0JFIF");
root.appendChild(jpegVariety);
root.appendChild(markerSequence);
jpegVariety.appendChild(app0JFIF);
meta.mergeTree(metadataFormat, root);
//Export jpg
Files.deleteIfExists(pOut);
ImageOutputStream ios = ImageIO.createImageOutputStream(pOut.toFile());
writer.setOutput(ios);
writer.write(meta, new IIOImage(bi, null, meta), param);
log.debug("Succeded writing jpeg with writer: " + writer.getClass().toString());
break;
}catch(Exception e) {
log.error("Failed writing jpeg with writer: " + (writer != null ? writer.getClass().toString():"null"));
log.error("Ex: " + e);
}
}
}
I'm getting an Exception thrown from JpegImageWriter, here is the relevant stack trace:
Ex: javax.imageio.IIOException: Metadata components != number of destination bands
File=null,Class=com.sun.imageio.plugins.jpeg.JPEGImageWriter,Method=checkSOFBands,Line=-1
File=null,Class=com.sun.imageio.plugins.jpeg.JPEGImageWriter,Method=writeOnThread,Line=-1
File=null,Class=com.sun.imageio.plugins.jpeg.JPEGImageWriter,Method=write,Line=-1
Also I know that the Buffered Image is a TYPE_BYTE_BINARY, and the raster has 1 band (I printed this in a debug message above). So the Exception message would make me think that I need to define in the app0JFIF metadata that we are exporting 1 band. I don't know how to define this though, does anyone have any experience with this? This metadata is difficult to work with, or is it just me?
Thanks in advance.
You are correct about needing one band. Here’s how I did it:
if (bi.getSampleModel().getNumBands() != 1) {
ColorModel colorModel = new ComponentColorModel(
ColorSpace.getInstance(ColorSpace.CS_GRAY), false, false,
Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
BufferedImage oneBandedImage = new BufferedImage(colorModel,
colorModel.createCompatibleWritableRaster(
bi.getWidth(), bi.getHeight()),
false, new Properties());
Graphics g = oneBandedImage.createGraphics();
g.drawImage(bi, 0, 0, null);
g.dispose();
bi = oneBandedImage;
}
After doing that, I didn’t need to directly obtain an ImageWriter and I didn’t need to set any metadata; ImageIO.write(bi, "JPEG", file) was sufficient.
I ran /usr/bin/file on the result, and got this:
JPEG image data, JFIF standard 1.02, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 315x180, components 1
I assume the components 1 part means that it has only one channel.
In the code i'm setting the alpha value of a pixel to 100 for entire image and I want the Alpha value to be 100 while reading the image. But at the retrieving part it gives me 255(Default Value) . What is wrong ? and how to solve it ? Any Help would be appreciated...
class Demo
{
Demo()
{
try
{
BufferedImage im2 = new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB);
File f2 = new File("test2.jpg");
im2 = ImageIO.read(f2);
int width1 = im2.getWidth();
int height1 = im2.getHeight();
for(int i=0;i<height1;i++)
{
for(int j=0;j<width1;j++)
{
Color c = new Color(50,0,0,100); //Set the alpha value to 100
im2.setRGB(j,i,c.getRGB()); // for every pixel
}
}
File f = new File("Demo_copy.jpg");
ImageIO.write(im2,"jpg",f);
// Retrieving.........
BufferedImage im1;
File f1 = new File("Demo_copy.jpg");
im1 = ImageIO.read(f1);
int width = im1.getWidth();
int height = im1.getHeight();
for(int i=0;i<height;i++)
{
for(int j=0;j<width;j++)
{
int pixel = im1.getRGB(j,i);
Color c = new Color(pixel,true);
int a = c.getAlpha();
System.out.println("Alpha value is :"+a); // Printing Alpha : 255 for every pixel
}
}
}catch(Exception e){}
}
public static void main(String [] ar)
{
new Demo();
}
}
The new BufferedImage(...) you assign to im2 is just thrown away (garbage collected) after you assign a new value from ImageIO.read(..). As the new value is a JPEG and doesn't have alpha, it does not matter what alpha values you set. They will always stay 255 (completely opaque).
Instead, you probably want to do something like this:
// Read opaque image...
BufferedImage img = ImageIO.read(new File("test2.jpg"));
// ...convert image to TYPE_INT_ARGB...
BufferedImage im2 = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g = im2.createGraphics();
try {
g.drawImage(img, 0, 0, null);
}
finally {
g.dispose();
}
// ... loop over and change alpha in im2 as before
Finally, you should write the image in a format that supports lossless alpha, like PNG instead of JPEG, to be sure you get the values you expect:
ImageIO.write(im2,"PNG", new File("Demo_copy.png"));
PS: It might just work using JPEG too, as the built-in Java ImageIO JPEG plugin supports reading/writing JPEGs with alpha values. However, most other software will misinterpret these as CMYK JPEGs, and the colors will look all wrong. Also, JPEG is lossy, so you will most likely not see the exact alpha value (100) as you would expect on the receiving end. That's why I suggest using PNG. TIFF or other format that supports alpha would also work, but requires extra plugins.
I have a pdf containing 2 blank images. I need to replace both the images with 2 separate images using PDFBox. The problem is, both the blank images appear to have the same resource. So, if I replace one, the other one is replaced with the same image as well.
I followed this example and tried overriding the processOperator() method and replaced the images based on the imageHeight. However, it still ends up replacing both the images with the same image. This is my code thus far:
protected void processOperator( PDFOperator operator, List arguments ) throws IOException
{
String operation = operator.getOperation();
if( INVOKE_OPERATOR.equals(operation) )
{
COSName objectName = (COSName)arguments.get( 0 );
Map<String, PDXObject> xobjects = getResources().getXObjects();
PDXObject xobject = (PDXObject)xobjects.get( objectName.getName() );
if( xobject instanceof PDXObjectImage )
{
PDXObjectImage blankImage = (PDXObjectImage)xobject;
int imageWidth = blankImage.getWidth();
int imageHeight = blankImage.getHeight();
System.out.println("Image width >>> "+imageWidth+" height >>>> "+imageHeight);
// Check if it is blank image 1 based on height
if(imageHeight < 480){
File logo = new File("abc.jpg");
BufferedImage bufferedImage = ImageIO.read(logo);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write( bufferedImage, "jpg", baos );
baos.flush();
byte[] logoImageInBytes = baos.toByteArray();
baos.close();
// label will be used to replace the blank image
label = logoImageInBytes;
}
BufferedImage img = ImageIO.read(new ByteArrayInputStream(label));
BufferedImage resizedImage = Scalr.resize(img, Scalr.Method.BALANCED, Scalr.Mode.FIT_EXACT, img.getWidth(), img.getHeight());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(resizedImage, "jpg", baos);
// Replace empty image in template with the image generated from shipping label byte array
PDXObjectImage validImage = new PDJpeg(doc, new ByteArrayInputStream(baos.toByteArray()));
blankImage.getCOSStream().replaceWithStream(validImage.getCOSStream());
}
Now, when I remove the if block which checks if (imageHeight < 480), it prints the imageHeight as 30 and 470 for the blank images. However, when I add the if block, it prints the imageHeight as 480 and 1500 and never goes inside the if block because of which both the blank images end up getting replaced by the same image.
What's going on here? I'm new to PDFBox, so I am unsure if my code is correct.
While first thinking about a generic way to actually replace the existing Image by the new Images, I agree with #TilmanHausherr that a more simple solution would be to simply add an extra content stream with two images in the size / position you need covering the existing Image.
This approach is easier to implement (even generically) and less error-prone than actual replacement.
In a generic solution we do not have the Image positions beforehand. To determine them, we can use this helper class (which essentially is a rip-off of the PDFBox example PrintImageLocations):
public class ImageLocator extends PDFStreamEngine
{
private static final String INVOKE_OPERATOR = "Do";
public ImageLocator() throws IOException
{
super(ResourceLoader.loadProperties("org/apache/pdfbox/resources/PDFTextStripper.properties", true));
}
public List<ImageLocation> getLocations()
{
return new ArrayList<ImageLocation>(locations);
}
protected void processOperator(PDFOperator operator, List<COSBase> arguments) throws IOException
{
String operation = operator.getOperation();
if (INVOKE_OPERATOR.equals(operation))
{
COSName objectName = (COSName) arguments.get(0);
Map<String, PDXObject> xobjects = getResources().getXObjects();
PDXObject xobject = (PDXObject) xobjects.get(objectName.getName());
if (xobject instanceof PDXObjectImage)
{
PDXObjectImage image = (PDXObjectImage) xobject;
PDPage page = getCurrentPage();
Matrix matrix = getGraphicsState().getCurrentTransformationMatrix();
locations.add(new ImageLocation(page, matrix, image));
}
else if (xobject instanceof PDXObjectForm)
{
// save the graphics state
getGraphicsStack().push((PDGraphicsState) getGraphicsState().clone());
PDPage page = getCurrentPage();
PDXObjectForm form = (PDXObjectForm) xobject;
COSStream invoke = (COSStream) form.getCOSObject();
PDResources pdResources = form.getResources();
if (pdResources == null)
{
pdResources = page.findResources();
}
// if there is an optional form matrix, we have to
// map the form space to the user space
Matrix matrix = form.getMatrix();
if (matrix != null)
{
Matrix xobjectCTM = matrix.multiply(getGraphicsState().getCurrentTransformationMatrix());
getGraphicsState().setCurrentTransformationMatrix(xobjectCTM);
}
processSubStream(page, pdResources, invoke);
// restore the graphics state
setGraphicsState((PDGraphicsState) getGraphicsStack().pop());
}
}
else
{
super.processOperator(operator, arguments);
}
}
public class ImageLocation
{
public ImageLocation(PDPage page, Matrix matrix, PDXObjectImage image)
{
this.page = page;
this.matrix = matrix;
this.image = image;
}
public PDPage getPage()
{
return page;
}
public Matrix getMatrix()
{
return matrix;
}
public PDXObjectImage getImage()
{
return image;
}
final PDPage page;
final Matrix matrix;
final PDXObjectImage image;
}
final List<ImageLocation> locations = new ArrayList<ImageLocation>();
}
(ImageLocator.java)
In contrast to the example class this helper stores the locations in a list instead of printing them.
We now can cover existing images using code like this:
try ( InputStream resource = getClass().getResourceAsStream("sample.pdf");
InputStream left = getClass().getResourceAsStream("left.png");
InputStream right = getClass().getResourceAsStream("right.png");
PDDocument document = PDDocument.load(resource) )
{
if (document.isEncrypted())
{
document.decrypt("");
}
PDJpeg leftImage = new PDJpeg(document, ImageIO.read(left));
PDJpeg rightImage = new PDJpeg(document, ImageIO.read(right));
// Locate images
ImageLocator locator = new ImageLocator();
List<?> allPages = document.getDocumentCatalog().getAllPages();
for (int i = 0; i < allPages.size(); i++)
{
PDPage page = (PDPage) allPages.get(i);
locator.processStream(page, page.findResources(), page.getContents().getStream());
}
// cover images
for (ImageLocation location : locator.getLocations())
{
// Decide on a replacement
PDRectangle cropBox = location.getPage().findCropBox();
float center = (cropBox.getLowerLeftX() + cropBox.getUpperRightX()) / 2.0f;
PDJpeg image = location.getMatrix().getXPosition() < center ? leftImage : rightImage;
AffineTransform transform = location.getMatrix().createAffineTransform();
PDPageContentStream content = new PDPageContentStream(document, location.getPage(), true, false, true);
content.drawXObject(image, transform);
content.close();
}
document.save(new File(RESULT_FOLDER, "sample-changed.pdf"));
}
(OverwriteImage)
This sample covers all images on the left half of their respective page with left.png and all others with right.png.
I have no implementation or example, but I want to illustrate you a possible way to do what you want by the following steps:
Since you need 2 Images (lets tell them imageA and imageB) in the pdf instead of 1 (which is the blank one). You have to add both of them to the pdf.
save the file temporary - optional, it could work without rewriting the pdf
reopen the file - optional, if you don't need step 2, you also don't need this step
Then replace the blank image with imageA or imageB
Remove the blank image from the pdf
Save the pdf
I have written a method that should compress a JPEG image using the ImageIO lib from Java. However, when I attempt to compress some images, their size actually increases! (from approx 21 kb to 36 kb). Any idea why this is happening? My code is shown below:
public boolean compressJPG(String originPath, String destinationPath){
float compression = 0.1f;
File in = new File(originPath);
File out = new File(destinationPath);
try {
RenderedImage image = ImageIO.read(in);
ImageWriter writer = null;
Iterator iter = ImageIO.getImageWritersByFormatName("jpg");
if(iter.hasNext()){
writer = (ImageWriter) iter.next();
}
ImageOutputStream outStream = ImageIO.createImageOutputStream(out);
writer.setOutput(outStream);
MyImageWriteParam iwparam = new MyImageWriteParam();
iwparam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT) ;
iwparam.setCompressionQuality(compression);
writer.write(null, new IIOImage(image, null, null), iwparam);
outStream.flush();
writer.dispose();
outStream.close();
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
public float setJPGCompressionQuality(float quality) {
if (quality < 0.0F || quality > 1.0F) {
throw new IllegalArgumentException("Quality out-of-bounds!");
}
return 256 - (quality * 256);
}
It may happened due to mode ImageWriteParam.MODE_EXPLICIT.
If the intersection is non-empty, writing will commence with the first subsampled pixel and include additional pixels within the intersected bounds according to the horizontal and vertical subsampling factors specified by IIOParam.setSourceSubsampling.
Use MODE_DEFAULT instead of MODE_EXPLICIT.
I have a BufferedImage:
BufferedImage bi = new BufferedImage(14400, 14400, BufferedImage.TYPE_INT_ARGB);
I have saved this image to a PNG file using the following code:
public static void saveGridImage(BufferedImage sourceImage, int DPI,
File output) throws IOException {
output.delete();
final String formatName = "png";
for (Iterator<ImageWriter> iw = ImageIO
.getImageWritersByFormatName(formatName); iw.hasNext();) {
ImageWriter writer = iw.next();
ImageWriteParam writeParam = writer.getDefaultWriteParam();
ImageTypeSpecifier typeSpecifier = ImageTypeSpecifier
.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB);
IIOMetadata metadata = writer.getDefaultImageMetadata(
typeSpecifier, writeParam);
if (metadata.isReadOnly()
|| !metadata.isStandardMetadataFormatSupported()) {
continue;
}
setDPI(metadata, DPI);
final ImageOutputStream stream = ImageIO
.createImageOutputStream(output);
try {
writer.setOutput(stream);
writer.write(metadata,
new IIOImage(sourceImage, null, metadata), writeParam);
} finally {
stream.close();
}
break;
}
}
public static void setDPI(IIOMetadata metadata, int DPI)
throws IIOInvalidTreeException {
double INCH_2_CM = 2.54;
// for PNG, it's dots per millimeter
double dotsPerMilli = 1.0 * DPI / 10 / INCH_2_CM;
IIOMetadataNode horiz = new IIOMetadataNode("HorizontalPixelSize");
horiz.setAttribute("value", Double.toString(dotsPerMilli));
IIOMetadataNode vert = new IIOMetadataNode("VerticalPixelSize");
vert.setAttribute("value", Double.toString(dotsPerMilli));
IIOMetadataNode dim = new IIOMetadataNode("Dimension");
dim.appendChild(horiz);
dim.appendChild(vert);
IIOMetadataNode root = new IIOMetadataNode("javax_imageio_1.0");
root.appendChild(dim);
metadata.mergeTree("javax_imageio_1.0", root);
}
When the code executes it creates an PNG file with 400 DPI and Disk Size of 168 MB; this is too much.
Is there any way or parameters I can use to save a smaller PNG?
Before, I had a 1.20 GB TIFF file, and when I converted it to PNG using imagemagick at 400 DPI, the resulting file size was only 700 KB.
So, I think I might be able to save the above file smaller.
Can pngj help me? Because I now have a png file which I can read in pngj library.
A 14400x14400 ARGB8 image has a raw (uncompressed) size of 791MB. It will compress more or less according to its nature (has uniform or smooth zones) and according (less important) to the PNG compression parameters.
when i convert it using imagemagic to PNG using 400 DPI , the
resulting file size is only 700 KB.
(I don't understand why you speak of DPI, that has nothing to do, what matters is the size in pixels) Are you saying that you are getting a 14400x14400 ARGB of 700KB? That would represent a compression of 1/1000, hard to believe unless the image is practically flat.
You should first understand what is going on here.
Anyway, here's a sample code with PNGJ
/** writes a BufferedImage of type TYPE_INT_ARGB to PNG using PNGJ */
public static void writeARGB(BufferedImage bi, OutputStream os) {
if(bi.getType() != BufferedImage.TYPE_INT_ARGB)
throw new PngjException("This method expects BufferedImage.TYPE_INT_ARGB" );
ImageInfo imi = new ImageInfo(bi.getWidth(), bi.getHeight(), 8, true);
PngWriter pngw = new PngWriter(os, imi);
pngw.setCompLevel(9);// maximum compression, not critical usually
pngw.setFilterType(FilterType.FILTER_AGGRESSIVE); // see what you prefer here
DataBufferInt db =((DataBufferInt) bi.getRaster().getDataBuffer());
SinglePixelPackedSampleModel samplemodel = (SinglePixelPackedSampleModel) bi.getSampleModel();
if(db.getNumBanks()!=1)
throw new PngjException("This method expects one bank");
ImageLine line = new ImageLine(imi);
for (int row = 0; row < imi.rows; row++) {
int elem=samplemodel.getOffset(0,row);
for (int col = 0,j=0; col < imi.cols; col++) {
int sample = db.getElem(elem++);
line.scanline[j++] = (sample & 0xFF0000)>>16; // R
line.scanline[j++] = (sample & 0xFF00)>>8; // G
line.scanline[j++] = (sample & 0xFF); // B
line.scanline[j++] = (((sample & 0xFF000000)>>24)&0xFF); // A
}
pngw.writeRow(line, row);
}
pngw.end();
}
I would attempt to fiddle with the settings on the writeParam object you're creating. Currently you're calling getDefaultWriteParam(); which gives you a basic writeParam object. My guess is the default would be NO compression.
After doing that, you can probably set some of the compression modes to reduce the file size.
writeParam.setCompressionMode(int mode);
writeParam.setCompressionQuality(float quality);
writeParam.setCompressionType(String compressionType);
See http://docs.oracle.com/javase/6/docs/api/javax/imageio/ImageWriteParam.html
And specifically http://docs.oracle.com/javase/6/docs/api/javax/imageio/ImageWriteParam.html#setCompressionMode(int)
sample code for pngj that works for 2.x versions of the leonbloy's pngj library
/** writes a BufferedImage of type TYPE_INT_ARGB to PNG using PNGJ */
public static void writePNGJARGB(BufferedImage bi, /*OutputStream os, */File file) {
System.out.println(".....entering PNGj alternative image file save mode....." );
if(bi.getType() != BufferedImage.TYPE_INT_ARGB) throw new PngjException("This method expects BufferedImage.TYPE_INT_ARGB" );
ImageInfo imi = new ImageInfo(bi.getWidth(), bi.getHeight(), 8, true);
PngWriter pngw = new PngWriter(file, imi, false);
// PngWriter pngw = new PngWriter(file,imginfo,overwrite); //params
pngw.setCompLevel(7); // tuning compression, not critical usually
pngw.setFilterType(FilterType.FILTER_PAETH); // tuning, see what you prefer here
System.out.println("..... PNGj metadata = "+pngw.getMetadata() );
DataBufferInt db =((DataBufferInt) bi.getRaster().getDataBuffer());
if(db.getNumBanks()!=1) {
throw new PngjException("This method expects one bank");
}
SinglePixelPackedSampleModel samplemodel = (SinglePixelPackedSampleModel) bi.getSampleModel();
ImageLineInt line = new ImageLineInt(imi);
int[] dbbuf = db.getData();
for (int row = 0; row < imi.rows; row++) {
int elem=samplemodel.getOffset(0,row);
for (int col = 0,j=0; col < imi.cols; col++) {
int sample = dbbuf[elem++];
line.getScanline()[j++] = (sample & 0xFF0000)>>16; // R
line.getScanline()[j++] = (sample & 0xFF00)>>8; // G
line.getScanline()[j++] = (sample & 0xFF); // B
line.getScanline()[j++] = (((sample & 0xFF000000)>>24)&0xFF); // A
}
//pngw.writeRow(line, /*imi.rows*/);
pngw.writeRow(line);
}
pngw.end();
}