28package com.jogamp.opengl.util;
30import java.io.BufferedInputStream;
31import java.io.IOException;
32import java.io.InputStream;
33import java.io.OutputStream;
34import java.nio.ByteBuffer;
35import java.nio.IntBuffer;
37import com.jogamp.nativewindow.util.Dimension;
38import com.jogamp.nativewindow.util.DimensionImmutable;
39import com.jogamp.nativewindow.util.PixelFormat;
40import com.jogamp.nativewindow.util.PixelRectangle;
41import com.jogamp.nativewindow.util.PixelFormatUtil;
43import jogamp.opengl.Debug;
44import jogamp.opengl.util.pngj.ImageInfo;
45import jogamp.opengl.util.pngj.ImageLine;
46import jogamp.opengl.util.pngj.ImageLineHelper;
47import jogamp.opengl.util.pngj.PngReader;
48import jogamp.opengl.util.pngj.PngWriter;
49import jogamp.opengl.util.pngj.chunks.PngChunkPLTE;
50import jogamp.opengl.util.pngj.chunks.PngChunkTRNS;
51import jogamp.opengl.util.pngj.chunks.PngChunkTextVar;
53import com.jogamp.common.nio.Buffers;
54import com.jogamp.common.util.IOUtil;
57 private static final boolean DEBUG = Debug.debug(
"PNG");
79 final PixelFormat ddestFmt,
final boolean destDirectBuffer,
final int destMinStrideInBytes,
80 final boolean destIsGLOriented)
throws IOException {
81 final BufferedInputStream bin = (in instanceof BufferedInputStream) ? (BufferedInputStream)in :
new BufferedInputStream(in);
82 final PngReader pngr =
new PngReader(bin,
null);
83 final ImageInfo imgInfo = pngr.imgInfo;
84 final PngChunkPLTE plte = pngr.getMetadata().getPLTE();
85 final PngChunkTRNS trns = pngr.getMetadata().getTRNS();
86 final boolean indexed = imgInfo.indexed;
87 final boolean hasAlpha = indexed ? ( trns != null ) : imgInfo.alpha ;
90 System.err.println(
"PNGPixelRect: "+imgInfo);
92 final int channels = indexed ? ( hasAlpha ? 4 : 3 ) : imgInfo.channels ;
93 final boolean isGrayAlpha = 2 == channels && imgInfo.greyscale && imgInfo.alpha;
94 if ( ! ( 1 == channels || 3 == channels || 4 == channels || isGrayAlpha ) ) {
95 throw new RuntimeException(
"PNGPixelRect can only handle Lum/RGB/RGBA [1/3/4 channels] or Lum+A (GA) images for now. Channels "+channels +
" Paletted: " + indexed);
97 final int bytesPerPixel = indexed ? channels : imgInfo.bytesPixel ;
98 if ( ! ( 1 == bytesPerPixel || 3 == bytesPerPixel || 4 == bytesPerPixel || isGrayAlpha ) ) {
99 throw new RuntimeException(
"PNGPixelRect can only handle Lum/RGB/RGBA [1/3/4 bpp] images for now. BytesPerPixel "+bytesPerPixel);
101 if( channels != bytesPerPixel ) {
102 throw new RuntimeException(
"PNGPixelRect currently only handles Channels [1/3/4] == BytePerPixel [1/3/4], channels: "+channels+
", bytesPerPixel "+bytesPerPixel);
104 final int width = imgInfo.cols;
105 final int height = imgInfo.rows;
106 final double dpiX, dpiY;
108 final double[] dpi = pngr.getMetadata().getDpi();
122 case 2: srcFmt = isGrayAlpha ? PixelFormat.LUMINANCE :
null;
break;
125 default: srcFmt =
null;
127 if(
null == srcFmt ) {
128 throw new InternalError(
"XXX: channels: "+channels+
", bytesPerPixel "+bytesPerPixel);
132 if(
null == ddestFmt ) {
141 final int destStrideInBytes = Math.max(destMinStrideInBytes, destFmt.
comp.
bytesPerPixel() * width);
142 final ByteBuffer destPixels = destDirectBuffer ? Buffers.newDirectByteBuffer(destStrideInBytes * height) :
143 ByteBuffer.allocate(destStrideInBytes * height);
145 final int reqBytes = destStrideInBytes * height;
146 if( destPixels.limit() < reqBytes ) {
147 throw new IndexOutOfBoundsException(
"Dest buffer has insufficient bytes left, needs "+reqBytes+
": "+destPixels);
150 final boolean vert_flip = destIsGLOriented;
152 int[] rgbaScanline = indexed ?
new int[width * channels] :
null;
154 System.err.println(
"PNGPixelRect: indexed "+indexed+
", alpha "+hasAlpha+
", grayscale "+imgInfo.greyscale+
", channels "+channels+
"/"+imgInfo.channels+
155 ", bytesPerPixel "+bytesPerPixel+
"/"+imgInfo.bytesPixel+
156 ", grayAlpha "+isGrayAlpha+
", pixels "+width+
"x"+height+
", dpi "+dpiX+
"x"+dpiY+
", format "+srcFmt);
157 System.err.println(
"PNGPixelRect: destFormat "+destFmt+
" ("+ddestFmt+
", fast-path "+(destFmt==srcFmt)+
"), destDirectBuffer "+destDirectBuffer+
", destIsGLOriented (flip) "+destIsGLOriented);
158 System.err.println(
"PNGPixelRect: destStrideInBytes "+destStrideInBytes+
" (destMinStrideInBytes "+destMinStrideInBytes+
")");
161 for (
int row = 0; row < height; row++) {
162 final ImageLine l1 = pngr.readRow(row);
164 int dataOff = vert_flip ? ( height - 1 - row ) * destStrideInBytes : row * destStrideInBytes;
166 for (
int j = width - 1; j >= 0; j--) {
167 rgbaScanline = ImageLineHelper.palette2rgb(l1, plte, trns, rgbaScanline);
168 dataOff = getPixelRGBA8ToAny(destFmt, destPixels, dataOff, rgbaScanline, lineOff, hasAlpha);
169 lineOff += bytesPerPixel;
171 }
else if( 1 == channels ) {
172 for (
int j = width - 1; j >= 0; j--) {
173 dataOff = getPixelLUMToAny(destFmt, destPixels, dataOff, (
byte)l1.scanline[lineOff++], (
byte)0xff);
175 }
else if( isGrayAlpha ) {
176 for (
int j = width - 1; j >= 0; j--) {
177 dataOff = getPixelLUMToAny(destFmt, destPixels, dataOff, (
byte)l1.scanline[lineOff++], (
byte)l1.scanline[lineOff++]);
179 }
else if( srcFmt == destFmt ) {
180 for (
int j = width - 1; j >= 0; j--) {
181 dataOff = getPixelRGBSame(destPixels, dataOff, l1.scanline, lineOff, bytesPerPixel);
182 lineOff += bytesPerPixel;
185 for (
int j = width - 1; j >= 0; j--) {
186 dataOff = getPixelRGBA8ToAny(destFmt, destPixels, dataOff, l1.scanline, lineOff, hasAlpha);
187 lineOff += bytesPerPixel;
193 return new PNGPixelRect(destFmt,
new Dimension(width, height), destStrideInBytes, destIsGLOriented, destPixels, dpiX, dpiY);
196 private static final int getPixelLUMToAny(
final PixelFormat dest_fmt,
final ByteBuffer d,
int dOff,
final byte lum,
final byte alpha) {
209 d.put(dOff++, alpha);
219 d.put(dOff++, alpha);
222 throw new InternalError(
"Unhandled format "+dest_fmt);
226 private static final int getPixelRGBA8ToAny(
final PixelFormat dest_fmt,
final ByteBuffer d,
int dOff,
final int[] scanline,
final int lineOff,
final boolean srcHasAlpha) {
227 final int p = PixelFormatUtil.convertToInt32(dest_fmt, (
byte)scanline[lineOff],
228 (
byte)scanline[lineOff+1],
229 (
byte)scanline[lineOff+2],
230 srcHasAlpha ? (
byte)scanline[lineOff+3] : (
byte)0xff);
231 final int dbpp = dest_fmt.comp.bytesPerPixel();
232 d.put(dOff++, (
byte) ( p ));
234 d.put(dOff++, (
byte) ( p >>> 8 ));
235 d.put(dOff++, (
byte) ( p >>> 16 ));
237 d.put(dOff++, (
byte) ( p >>> 24 ));
242 private static final int getPixelRGBSame(
final ByteBuffer d,
int dOff,
final int[] scanline,
final int lineOff,
final int bpp) {
243 d.put(dOff++, (
byte)scanline[lineOff]);
245 d.put(dOff++, (
byte)scanline[lineOff + 1]);
246 d.put(dOff++, (
byte)scanline[lineOff + 2]);
248 d.put(dOff++, (
byte)scanline[lineOff + 3]);
253 private int setPixelRGBA8(
final ImageLine line,
final int lineOff,
final ByteBuffer src,
final int srcOff,
final int bytesPerPixel,
final boolean hasAlpha) {
254 final int b = hasAlpha ? 4-1 : 3-1;
255 if( src.limit() <= srcOff + b ) {
256 throw new IndexOutOfBoundsException(
"Buffer has unsufficient bytes left, needs ["+srcOff+
".."+(srcOff+b)+
"]: "+src);
258 final int p = PixelFormatUtil.convertToInt32(hasAlpha ? PixelFormat.RGBA8888 : PixelFormat.RGB888,
pixelformat, src, srcOff);
259 line.scanline[lineOff ] = 0xff & p;
260 line.scanline[lineOff + 1] = 0xff & ( p >>> 8 );
261 line.scanline[lineOff + 2] = 0xff & ( p >>> 16 );
263 line.scanline[lineOff + 3] = 0xff & ( p >>> 24 );
268 private static void setPixelRGBA8(
final PixelFormat
pixelformat,
final ImageLine line,
final int lineOff,
final int srcPix,
final int bytesPerPixel,
final boolean hasAlpha) {
269 final int p = PixelFormatUtil.convertToInt32(hasAlpha ? PixelFormat.RGBA8888 : PixelFormat.RGB888,
pixelformat, srcPix);
270 line.scanline[lineOff ] = 0xff & p;
271 line.scanline[lineOff + 1] = 0xff & ( p >>> 8 );
272 line.scanline[lineOff + 2] = 0xff & ( p >>> 16 );
274 line.scanline[lineOff + 3] = 0xff & ( p >>> 24 );
292 final double dpiX,
final double dpiY) {
294 this.dpi =
new double[] { dpiX, dpiY };
298 this.dpi =
new double[] { dpiX, dpiY };
300 private final double[] dpi;
305 public void write(
final OutputStream outstream,
final boolean closeOutstream)
throws IOException {
309 final ImageInfo imi =
new ImageInfo(width, height, 8 ,
310 (4 == bytesPerPixel) ?
true :
false ,
311 (1 == bytesPerPixel) ?
true :
false ,
316 final PngWriter png =
new PngWriter(outstream, imi);
318 png.getMetadata().setDpi(dpi[0], dpi[1]);
319 png.getMetadata().setTimeNow(0);
320 png.getMetadata().setText(PngChunkTextVar.KEY_Title,
"JogAmp PNGPixelRect");
321 final boolean hasAlpha = 4 == bytesPerPixel;
323 final ImageLine l1 =
new ImageLine(imi);
324 for (
int row = 0; row < height; row++) {
327 if(1 == bytesPerPixel) {
328 for (
int j = width - 1; j >= 0; j--) {
329 l1.scanline[lineOff++] =
pixels.get(dataOff++);
332 for (
int j = width - 1; j >= 0; j--) {
333 dataOff = setPixelRGBA8(l1, lineOff,
pixels, dataOff, bytesPerPixel, hasAlpha);
334 lineOff += bytesPerPixel;
337 png.writeRow(l1, row);
341 if( closeOutstream ) {
342 IOUtil.close(outstream,
false);
349 final double dpiX,
final double dpiY,
350 final OutputStream outstream,
final boolean closeOutstream)
throws IOException {
354 final ImageInfo imi =
new ImageInfo(width, height, 8 ,
355 (4 == bytesPerPixel) ?
true :
false ,
356 (1 == bytesPerPixel) ?
true :
false ,
358 if( 0 != strideInPixels ) {
360 throw new IllegalArgumentException(
"Invalid stride "+bytesPerPixel+
", must be greater than width "+
size.
getWidth());
366 if(
pixels.limit() < reqPixels ) {
367 throw new IndexOutOfBoundsException(
"Dest buffer has insufficient pixels left, needs "+reqPixels+
": "+
pixels);
372 final PngWriter png =
new PngWriter(outstream, imi);
374 png.getMetadata().setDpi(dpiX, dpiY);
375 png.getMetadata().setTimeNow(0);
376 png.getMetadata().setText(PngChunkTextVar.KEY_Title,
"JogAmp PNGPixelRect");
377 final boolean hasAlpha = 4 == bytesPerPixel;
379 final ImageLine l1 =
new ImageLine(imi);
380 for (
int row = 0; row < height; row++) {
381 int dataOff =
isGLOriented ? ( height - 1 - row ) * strideInPixels : row * strideInPixels;
383 if(1 == bytesPerPixel) {
384 for (
int j = width - 1; j >= 0; j--) {
385 l1.scanline[lineOff++] =
pixels.get(dataOff++);
388 for (
int j = width - 1; j >= 0; j--) {
389 setPixelRGBA8(
pixelformat, l1, lineOff,
pixels.get(dataOff++), bytesPerPixel, hasAlpha);
390 lineOff += bytesPerPixel;
393 png.writeRow(l1, row);
397 if( closeOutstream ) {
398 IOUtil.close(outstream,
false);
Generic PixelRectangle implementation.
final PixelFormat pixelformat
final DimensionImmutable size
final boolean isGLOriented
void write(final OutputStream outstream, final boolean closeOutstream)
static void write(final PixelFormat pixelformat, final DimensionImmutable size, int strideInPixels, final boolean isGLOriented, final IntBuffer pixels, final double dpiX, final double dpiY, final OutputStream outstream, final boolean closeOutstream)
static PNGPixelRect read(final InputStream in, final PixelFormat ddestFmt, final boolean destDirectBuffer, final int destMinStrideInBytes, final boolean destIsGLOriented)
Reads a PNG image from the specified InputStream.
PNGPixelRect(final PixelRectangle src, final double dpiX, final double dpiY)
double[] getDpi()
Returns the dpi of the image.
PNGPixelRect(final PixelFormat pixelformat, final DimensionImmutable size, final int strideInBytes, final boolean isGLOriented, final ByteBuffer pixels, final double dpiX, final double dpiY)
Creates a PNGPixelRect from data supplied by the end user.
Immutable Dimension Interface, consisting of it's read only components:
Pixel Rectangle identified by it's hashCode().