public void write(File out, boolean allowOverwrite) throws IOException { final ImageInfo imi = new ImageInfo( pixelWidth, pixelHeight, 8, (4 == bytesPerPixel) ? true : false); // 8 bits per channel, no alpha // open image for writing to a output stream final OutputStream outs = new BufferedOutputStream(IOUtil.getFileOutputStream(out, allowOverwrite)); try { final PngWriter png = new PngWriter(outs, imi); // add some optional metadata (chunks) png.getMetadata().setDpi(dpi[0], dpi[1]); png.getMetadata().setTimeNow(0); // 0 seconds fron now = now png.getMetadata().setText(PngChunkTextVar.KEY_Title, "JogAmp PNGImage"); // png.getMetadata().setText("my key", "my text"); final boolean hasAlpha = 4 == bytesPerPixel; final ImageLine l1 = new ImageLine(imi); if (isGLOriented) { // start at last pixel at end-of-buffer, reverse read (OpenGL bottom-left -> PNG top-left // origin) int dataOff = (pixelWidth * bytesPerPixel * (pixelHeight - 1)) + // full lines - 1 line ((pixelWidth - 1) * bytesPerPixel); // one line - 1 pixel for (int row = 0; row < pixelHeight; row++) { int lineOff = (pixelWidth - 1) * bytesPerPixel; // start w/ last pixel in line, reverse store (OpenGL bottom-left // -> PNG top-left origin) if (1 == bytesPerPixel) { for (int j = pixelWidth - 1; j >= 0; j--) { l1.scanline[lineOff--] = data.get(dataOff--); // // Luminance, 1 bytesPerPixel } } else { for (int j = pixelWidth - 1; j >= 0; j--) { dataOff = setPixelRGBA8(l1, lineOff, data, dataOff, hasAlpha); lineOff -= bytesPerPixel; } } png.writeRow(l1, row); } } else { int dataOff = 0; // start at first pixel at start-of-buffer, normal read (same origin: top-left) for (int row = 0; row < pixelHeight; row++) { int lineOff = 0; // start w/ first pixel in line, normal store (same origin: top-left) if (1 == bytesPerPixel) { for (int j = pixelWidth - 1; j >= 0; j--) { l1.scanline[lineOff++] = data.get(dataOff++); // // Luminance, 1 bytesPerPixel } } else { for (int j = pixelWidth - 1; j >= 0; j--) { dataOff = setPixelRGBA8(l1, lineOff, data, dataOff, hasAlpha); lineOff += bytesPerPixel; } } png.writeRow(l1, row); } } png.end(); } finally { IOUtil.close(outs, false); } }
private void writeImage( final File file, byte[] data, final int xsize, final int ysize, final int zsize, final boolean yflip) throws IOException { // Input data is in RGBRGBRGB or RGBARGBARGBA format; first unswizzle it final byte[] tmpData = new byte[xsize * ysize * zsize]; int dest = 0; for (int i = 0; i < zsize; i++) { for (int j = i; j < (xsize * ysize * zsize); j += zsize) { tmpData[dest++] = data[j]; } } data = tmpData; // requires: DATA must be an array of size XSIZE * YSIZE * ZSIZE, // indexed in the following manner: // data[0] ...data[xsize-1] == first row of first channel // data[xsize]...data[2*xsize-1] == second row of first channel // ... data[(ysize - 1) * xsize]...data[(ysize * xsize) - 1] == // last row of first channel // Later channels follow the same format. // *** NOTE that "first row" is defined by the BOTTOM ROW of // the image. That is, the origin is in the lower left corner. // effects: writes out an SGI image to FILE, RLE-compressed, INCLUDING // header, of dimensions (xsize, ysize, zsize), and containing // the data in DATA. If YFLIP is set, outputs the data in DATA // in reverse order vertically (equivalent to a flip about the // x axis). // Build the offset tables final int[] starttab = new int[ysize * zsize]; final int[] lengthtab = new int[ysize * zsize]; // Temporary buffer for holding RLE data. // Note that this makes the assumption that RLE-compressed data will // never exceed twice the size of the input data. // There are surely formal proofs about how big the RLE buffer should // be, as well as what the optimal look-ahead size is (i.e. don't switch // copy/repeat modes for less than N repeats). However, I'm going from // empirical evidence here; the break-even point seems to be a look- // ahead of 3. (That is, if the three values following this one are all // the same as the current value, switch to repeat mode.) final int lookahead = 3; final byte[] rlebuf = new byte[2 * xsize * ysize * zsize]; int cur_loc = 0; // current offset location. int ptr = 0; int total_size = 0; int ystart = 0; int yincr = 1; int yend = ysize; if (yflip) { ystart = ysize - 1; yend = -1; yincr = -1; } final boolean DEBUG = false; for (int z = 0; z < zsize; z++) { for (int y = ystart; y != yend; y += yincr) { // RLE-compress each row. int x = 0; byte count = 0; boolean repeat_mode = false; boolean should_switch = false; final int start_ptr = ptr; int num_ptr = ptr++; byte repeat_val = 0; while (x < xsize) { // see if we should switch modes should_switch = false; if (repeat_mode) { if (imgref(data, x, y, z, xsize, ysize, zsize) != repeat_val) { should_switch = true; } } else { // look ahead to see if we should switch to repeat mode. // stay within the scanline for the lookahead if ((x + lookahead) < xsize) { should_switch = true; for (int i = 1; i <= lookahead; i++) { if (DEBUG) System.err.println( "left side was " + ((int) imgref(data, x, y, z, xsize, ysize, zsize)) + ", right side was " + (int) imgref(data, x + i, y, z, xsize, ysize, zsize)); if (imgref(data, x, y, z, xsize, ysize, zsize) != imgref(data, x + i, y, z, xsize, ysize, zsize)) should_switch = false; } } } if (should_switch || (count == 127)) { // update the number of elements we repeated/copied if (x > 0) { if (repeat_mode) rlebuf[num_ptr] = count; else rlebuf[num_ptr] = (byte) (count | 0x80); } // perform mode switch if necessary; output repeat_val if // switching FROM repeat mode, and set it if switching // TO repeat mode. if (repeat_mode) { if (should_switch) repeat_mode = false; rlebuf[ptr++] = repeat_val; } else { if (should_switch) repeat_mode = true; repeat_val = imgref(data, x, y, z, xsize, ysize, zsize); } if (x > 0) { // reset the number pointer num_ptr = ptr++; // reset number of bytes copied count = 0; } } // if not in repeat mode, copy element to ptr if (!repeat_mode) { rlebuf[ptr++] = imgref(data, x, y, z, xsize, ysize, zsize); } count++; if (x == xsize - 1) { // Need to store the number of pixels we copied/repeated. if (repeat_mode) { rlebuf[num_ptr] = count; // If we ended the row in repeat mode, store the // repeated value rlebuf[ptr++] = repeat_val; } else rlebuf[num_ptr] = (byte) (count | 0x80); // output zero counter for the last value in the row rlebuf[ptr++] = 0; } x++; } // output this row's length into the length table final int rowlen = ptr - start_ptr; if (yflip) lengthtab[ysize * z + (ysize - y - 1)] = rowlen; else lengthtab[ysize * z + y] = rowlen; // add to the start table, and update the current offset if (yflip) starttab[ysize * z + (ysize - y - 1)] = cur_loc; else starttab[ysize * z + y] = cur_loc; cur_loc += rowlen; } } // Now we have the offset tables computed, as well as the RLE data. // Output this information to the file. total_size = ptr; if (DEBUG) System.err.println("total_size was " + total_size); final DataOutputStream stream = new DataOutputStream(new BufferedOutputStream(IOUtil.getFileOutputStream(file, true))); writeHeader(stream, xsize, ysize, zsize, true); final int SIZEOF_INT = 4; for (int i = 0; i < (ysize * zsize); i++) stream.writeInt(starttab[i] + 512 + (2 * ysize * zsize * SIZEOF_INT)); for (int i = 0; i < (ysize * zsize); i++) stream.writeInt(lengthtab[i]); for (int i = 0; i < total_size; i++) stream.write(rlebuf[i]); stream.close(); }