1 29 30 package nextapp.echo2.webcontainer.image; 31 32 import java.awt.Image ; 33 import java.awt.image.BufferedImage ; 34 import java.awt.image.DataBuffer ; 35 import java.awt.image.IndexColorModel ; 36 import java.awt.image.Raster ; 37 import java.io.ByteArrayOutputStream ; 38 import java.io.IOException ; 39 import java.io.OutputStream ; 40 import java.util.Arrays ; 41 import java.util.zip.CheckedOutputStream ; 42 import java.util.zip.Checksum ; 43 import java.util.zip.CRC32 ; 44 import java.util.zip.Deflater ; 45 import java.util.zip.DeflaterOutputStream ; 46 47 52 public class PngEncoder { 53 54 public static final Filter SUB_FILTER = new SubFilter(); 55 public static final Filter UP_FILTER = new UpFilter(); 56 public static final Filter AVERAGE_FILTER = new AverageFilter(); 57 public static final Filter PAETH_FILTER = new PaethFilter(); 58 59 private static final byte[] SIGNATURE = { (byte)0x89, (byte)0x50, (byte)0x4e, (byte)0x47, 60 (byte)0x0d, (byte)0x0a, (byte)0x1a, (byte)0x0a }; 61 private static final byte[] IHDR = { (byte) 'I', (byte) 'H', (byte) 'D', (byte) 'R' }; 62 private static final byte[] PLTE = { (byte) 'P', (byte) 'L', (byte) 'T', (byte) 'E' }; 63 private static final byte[] IDAT = { (byte) 'I', (byte) 'D', (byte) 'A', (byte) 'T' }; 64 private static final byte[] IEND = { (byte) 'I', (byte) 'E', (byte) 'N', (byte) 'D' }; 65 66 private static final int SUB_FILTER_TYPE = 1; 67 private static final int UP_FILTER_TYPE = 2; 68 private static final int AVERAGE_FILTER_TYPE = 3; 69 private static final int PAETH_FILTER_TYPE = 4; 70 71 private static final byte BIT_DEPTH = (byte) 8; 72 73 private static final byte COLOR_TYPE_INDEXED = (byte) 3; 74 private static final byte COLOR_TYPE_RGB = (byte) 2; 75 private static final byte COLOR_TYPE_RGBA = (byte) 6; 76 77 private static final int[] INT_TRANSLATOR_CHANNEL_MAP = new int[]{2, 1, 0, 3}; 78 79 85 private static void writeInt(OutputStream out, int i) 86 throws IOException { 87 out.write(new byte[]{(byte) (i >> 24), 88 (byte) ((i >> 16) & 0xff), 89 (byte) ((i >> 8) & 0xff), 90 (byte) (i & 0xff)}); 91 } 92 93 98 public interface Filter { 99 100 110 public void filter(byte[] filterOutput, byte[] currentRow, byte[] previousRow, int outputBpp); 111 112 115 public int getType(); 116 } 117 118 121 private static class SubFilter 122 implements Filter { 123 124 127 public void filter(byte[] filterOutput, byte[] currentRow, byte[] previousRow, int outputBpp) { 128 for (int index = 0; index < filterOutput.length; ++index) { 129 if (index < outputBpp) { 130 filterOutput[index] = currentRow[index]; 131 } else { 132 filterOutput[index] = (byte) (currentRow[index] - currentRow[index - outputBpp]); 133 } 134 } 135 } 136 137 140 public int getType() { 141 return SUB_FILTER_TYPE; 142 } 143 } 144 145 148 private static class UpFilter 149 implements Filter { 150 151 154 public void filter(byte[] filterOutput, byte[] currentRow, byte[] previousRow, int outputBpp) { 155 for (int index = 0; index < currentRow.length; ++index) { 156 filterOutput[index] = (byte) (currentRow[index] - previousRow[index]); 157 } 158 } 159 160 163 public int getType() { 164 return UP_FILTER_TYPE; 165 } 166 } 167 168 171 private static class AverageFilter 172 implements Filter { 173 174 177 public void filter(byte[] filterOutput, byte[] currentRow, byte[] previousRow, int outputBpp) { 178 int w, n; 179 180 for (int index = 0; index < filterOutput.length; ++index) { 181 n = (previousRow[index] + 0x100) & 0xff; 182 if (index < outputBpp) { 183 w = 0; 184 } else { 185 w = (currentRow[index - outputBpp] + 0x100) & 0xff; 186 } 187 filterOutput[index] = (byte) (currentRow[index] - (byte) ((w + n) / 2)); 188 } 189 } 190 191 194 public int getType() { 195 return AVERAGE_FILTER_TYPE; 196 } 197 } 198 199 202 private static class PaethFilter 203 implements Filter { 204 205 208 public void filter(byte[] filterOutput, byte[] currentRow, byte[] previousRow, int outputBpp) { 209 byte pv; 210 int n, w, nw, p, pn, pw, pnw; 211 212 for (int index = 0; index < filterOutput.length; ++index) { 213 n = (previousRow[index] + 0x100) & 0xff; 214 if (index < outputBpp) { 215 w = 0; 216 nw = 0; 217 } else { 218 w = (currentRow[index - outputBpp] + 0x100) & 0xff; 219 nw = (previousRow[index - outputBpp] + 0x100) & 0xff; 220 } 221 222 p = w + n - nw; 223 pw = Math.abs(p - w); 224 pn = Math.abs(p - n); 225 pnw = Math.abs(p - w); 226 if (pw <= pn && pw <= pnw) { 227 pv = (byte) w; 228 } else if (pn <= pnw) { 229 pv = (byte) n; 230 } else { 231 pv = (byte) nw; 232 } 233 234 filterOutput[index] = (byte) (currentRow[index] - pv); 235 } 236 } 237 238 241 public int getType() { 242 return PAETH_FILTER_TYPE; 243 } 244 } 245 246 252 interface Translator { 253 254 262 public void translate(byte[] outputPixelQueue, int row); 263 } 264 265 268 private class ByteTranslator 269 implements Translator { 270 271 int rowWidth = width * outputBpp; byte[] inputPixelQueue = new byte[rowWidth + outputBpp]; 273 int column; 274 int channel; 275 276 279 public void translate(byte[] outputPixelQueue, int row) { 280 raster.getDataElements(0, row, width, 1, inputPixelQueue); 281 for (column = 0; column < width; ++column) { 282 for (channel = 0; channel < outputBpp; ++channel) { 283 outputPixelQueue[column * outputBpp + channel] 284 = inputPixelQueue[column * inputBpp + channel]; 285 } 286 } 287 } 288 } 289 290 293 private class IntTranslator 294 implements Translator { 295 296 int[] inputPixelQueue = new int[width]; 297 int column; 298 int channel; 299 300 303 public void translate(byte[] outputPixelQueue, int row) { 304 305 image.getRGB(0, row, width, 1, inputPixelQueue, 0, width); 306 307 311 for (column = 0; column < width; ++column) { 312 for (channel = 0; channel < outputBpp; ++channel) { 313 outputPixelQueue[column * outputBpp + channel] 314 = (byte) (inputPixelQueue[column] >> (INT_TRANSLATOR_CHANNEL_MAP[channel] * 8)); 315 } 316 } 317 } 318 } 319 320 private BufferedImage image; 321 private Filter filter; 322 private int compressionLevel; 323 private int width; 324 private int height; 325 private int transferType; 326 private Raster raster; 327 private int inputBpp; 328 private int outputBpp; 329 private Translator translator; 330 331 351 public PngEncoder(Image image, boolean encodeAlpha, Filter filter, int compressionLevel) { 352 super(); 353 354 this.image = ImageToBufferedImage.toBufferedImage(image); 355 this.filter = filter; 356 this.compressionLevel = compressionLevel; 357 358 width = this.image.getWidth(null); 359 height = this.image.getHeight(null); 360 raster = this.image.getRaster(); 361 transferType = raster.getTransferType(); 362 363 int dataBytes = raster.getNumDataElements(); 365 if (transferType == DataBuffer.TYPE_BYTE && dataBytes == 4) { 366 outputBpp = encodeAlpha ? 4 : 3; 367 inputBpp = 4; 368 translator = new ByteTranslator(); 369 } else if (transferType == DataBuffer.TYPE_BYTE && dataBytes == 3) { 370 outputBpp = 3; 371 inputBpp = 3; 372 encodeAlpha = false; 373 translator = new ByteTranslator(); 374 } else if (transferType == DataBuffer.TYPE_INT && dataBytes == 1) { 375 outputBpp = encodeAlpha ? 4 : 3; 376 inputBpp = 4; 377 translator = new IntTranslator(); 378 } else if (transferType == DataBuffer.TYPE_BYTE && dataBytes == 1) { 379 throw new UnsupportedOperationException ("Encoding indexed-color images not yet supported."); 380 } else { 381 throw new IllegalArgumentException ( 382 "Cannot determine appropriate bits-per-pixel for provided image."); 383 } 384 } 385 386 393 public synchronized void encode(OutputStream out) 394 throws IOException { 395 Checksum csum = new CRC32 (); 396 out = new CheckedOutputStream (out, csum); 397 398 out.write(SIGNATURE); 399 400 writeIhdrChunk(out, csum); 401 402 if (outputBpp == 1) { 403 writePlteChunk(out, csum); 404 } 405 406 writeIdatChunks(out, csum); 407 408 writeIendChunk(out, csum); 409 } 410 411 419 private void writeIdatChunks(OutputStream out, Checksum csum) 420 throws IOException { 421 int rowWidth = width * outputBpp; 423 int row = 0; 424 425 Deflater deflater = new Deflater (compressionLevel); 426 ByteArrayOutputStream byteOut = new ByteArrayOutputStream (); 427 DeflaterOutputStream defOut = new DeflaterOutputStream (byteOut, deflater); 428 429 byte[] filteredPixelQueue = new byte[rowWidth]; 430 431 byte[][] outputPixelQueue = new byte[2][rowWidth]; 433 Arrays.fill(outputPixelQueue[1], (byte) 0); 434 int outputPixelQueueRow = 0; 435 int outputPixelQueuePrevRow = 1; 436 437 while (row < height) { 438 if (filter == null) { 439 defOut.write(0); 440 translator.translate(outputPixelQueue[outputPixelQueueRow], row); 441 defOut.write(outputPixelQueue[outputPixelQueueRow], 0, rowWidth); 442 } else { 443 defOut.write(filter.getType()); 444 translator.translate(outputPixelQueue[outputPixelQueueRow], row); 445 filter.filter(filteredPixelQueue, outputPixelQueue[outputPixelQueueRow], 446 outputPixelQueue[outputPixelQueuePrevRow], outputBpp); 447 defOut.write(filteredPixelQueue, 0, rowWidth); 448 } 449 450 ++row; 451 outputPixelQueueRow = row & 1; 452 outputPixelQueuePrevRow = outputPixelQueueRow ^ 1; 453 } 454 defOut.finish(); 455 byteOut.close(); 456 457 writeInt(out, byteOut.size()); 458 csum.reset(); 459 out.write(IDAT); 460 byteOut.writeTo(out); 461 writeInt(out, (int) csum.getValue()); 462 } 463 464 472 private void writeIendChunk(OutputStream out, Checksum csum) 473 throws IOException { 474 writeInt(out, 0); 475 csum.reset(); 476 out.write(IEND); 477 writeInt(out, (int) csum.getValue()); 478 } 479 480 488 private void writeIhdrChunk(OutputStream out, Checksum csum) 489 throws IOException { 490 writeInt(out, 13); csum.reset(); 492 out.write(IHDR); 493 writeInt(out, width); 494 writeInt(out, height); 495 out.write(BIT_DEPTH); 496 switch (outputBpp) { 497 case 1: 498 out.write(COLOR_TYPE_INDEXED); 499 break; 500 case 3: 501 out.write(COLOR_TYPE_RGB); 502 break; 503 case 4: 504 out.write(COLOR_TYPE_RGBA); 505 break; 506 default: 507 throw new IllegalStateException ("Invalid bytes per pixel"); 508 } 509 out.write(0); out.write(0); out.write(0); writeInt(out, (int) csum.getValue()); 513 } 514 515 523 private void writePlteChunk(OutputStream out, Checksum csum) 524 throws IOException { 525 IndexColorModel icm = (IndexColorModel ) image.getColorModel(); 526 527 writeInt(out, 768); csum.reset(); 529 out.write(PLTE); 530 531 byte[] reds = new byte[256]; 532 icm.getReds(reds); 533 534 byte[] greens = new byte[256]; 535 icm.getGreens(greens); 536 537 byte[] blues = new byte[256]; 538 icm.getBlues(blues); 539 540 for (int index = 0; index < 256; ++index) { 541 out.write(reds[index]); 542 out.write(greens[index]); 543 out.write(blues[index]); 544 } 545 546 writeInt(out, (int) csum.getValue()); 547 } 548 } 549 | Popular Tags |