Skip to content

Commit

Permalink
Add PngStreamSource
Browse files Browse the repository at this point in the history
- Add PngStreamSource as alternative to PngAtOnceSource.
- Not sure about keeping the at-once source.
- Added first cut (incomplete!) of PngHex command-line program.
- Added the missing 16-bit RGBA decoder.
  • Loading branch information
aellerton committed Jun 13, 2015
1 parent 93545ad commit e6d5971
Show file tree
Hide file tree
Showing 20 changed files with 406 additions and 30 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,14 @@ other custom chunk producer).

## History

0.5.0: Initial Release. 20150611
v0.5.0: 20150611
- Initial Release.

v0.5.1: 20150613
- Add PngStreamSource as alternative to PngAtOnceSource.
- Not sure about keeping the at-once source.
- Added first cut (incomplete!) of PngHex command-line program.
- Added the missing 16-bit RGBA decoder.


***
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ public boolean hasDefaultImage() {
return defaultImageIsSet;
}

public boolean hasAnimation() {
return null != animationControl && !animationFrames.isEmpty();
public boolean isAnimated() {
return null != animationControl && animationControl.numFrames > 0;
}

public PngAnimationControl getAnimationControl() {
Expand All @@ -57,6 +57,7 @@ public List<Frame> getAnimationFrames() {
return animationFrames;
}


public static class Frame {
public final PngFrameControl control;
public final Argb8888Bitmap bitmap;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ public void processTransparency(byte[] bytes, int position, int length) throws P

@Override
public void processDefaultImageData(InputStream inputStream, PngChunkCode code, int position, int length) throws IOException, PngException {
//super.processDefaultImageData(inputStream, code, position, length);
if (!builder.wantDefaultImage()) {
inputStream.skip(length); // important!
return;
}

Expand Down Expand Up @@ -140,6 +140,7 @@ public void processFrameControl(PngFrameControl frameControl) throws PngExceptio
public void processFrameImageData(InputStream inputStream, PngChunkCode code, int position, int length) throws IOException, PngException {
//throw new PngFeatureException("PngArgb8888Processor does not support animation frames");
if (!builder.wantAnimationFrames()) {
inputStream.skip(length);
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,15 @@ public static Argb8888ScanlineProcessor from(PngHeader header, PngScanlineBuffer
}

case PNG_TRUECOLOUR_WITH_ALPHA:
if (header.bitDepth != 8) throw new PngFeatureException(String.format("Only 8-bit truecolour is supported, not %d", header.bitDepth));
return new Truecolour8Alpha(bytesPerScanline, bitmap);
switch (header.bitDepth) {
case 8:
return new Truecolour8Alpha(bytesPerScanline, bitmap);
case 16:
return new Truecolour16Alpha(bytesPerScanline, bitmap);
default:
throw new PngIntegrityException(String.format("Invalid truecolour with alpha bit-depth: %d", header.bitDepth)); // TODO: should be in header parse.

}

default:
throw new PngFeatureException("ARGB8888 doesn't support PNG mode "+header.colourType.name());
Expand Down Expand Up @@ -396,4 +403,42 @@ public Argb8888ScanlineProcessor clone(int bytesPerRow, Argb8888Bitmap bitmap) {
}
}


/**
* Transforms true-colour with alpha (RGBA) 16-bit source pixels to ARGB8888 pixels.
*
* Note that the simpler method of resampling the colour is done, namely discard the LSB.
*/
public static class Truecolour16Alpha extends Argb8888ScanlineProcessor {
public Truecolour16Alpha(int bytesPerScanline, Argb8888Bitmap bitmap) {
super(bytesPerScanline, bitmap);
}

@Override
public void processScanline(byte[] srcBytes, int srcPosition) {
final int[] destArray= this.bitmap.array;
final int width = this.bitmap.width;
//final int alpha = 0xff000000; // No alpha in the image means every pixel must be fully opaque
int writePosition = this.y * width;
//srcPosition++; // skip filter byte
for (int x=0; x< width; x++) {
final int r = 0xff & srcBytes[srcPosition];
srcPosition += 2; // skip the byte just read and the least significant byte of the next
final int g = 0xff & srcBytes[srcPosition];
srcPosition += 2; // ditto
final int b = 0xff & srcBytes[srcPosition];
srcPosition += 2; // ditto again
final int alpha = 0xff & srcBytes[srcPosition];
srcPosition += 2; // skip the byte just read and the least significant byte of the next
final int c = alpha << 24 | r << 16 | g << 8 | b;
destArray[writePosition++] = c;
}
this.y++;
}

@Override
public Argb8888ScanlineProcessor clone(int bytesPerRow, Argb8888Bitmap bitmap) {
return new Truecolour16(bytesPerRow, bitmap);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package net.ellerton.japng.util;
package net.ellerton.japng.argb8888;

import net.ellerton.japng.argb8888.Argb8888Bitmap;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,19 @@ public DefaultPngChunkReader(PngChunkProcessor<ResultT> processor) {
public boolean readChunk(PngSource source, int code, int dataLength) throws PngException, IOException {
int dataPosition = source.tell(); // note the start position before any further reads are done.

if (dataLength < 0) {
throw new PngIntegrityException(String.format("Corrupted read (Data length %d)", dataLength));
}

switch(code) {
case PngConstants.IHDR_VALUE:
readHeaderChunk(source, dataLength);
break;

case PngConstants.IEND_VALUE:
// NOP
break;

case PngConstants.gAMA_VALUE:
readGammaChunk(source, dataLength);
break;
Expand Down Expand Up @@ -161,6 +169,7 @@ public void readImageDataChunk(PngSource source, int dataLength) throws PngExcep
switch (animationType) {
case ANIMATED_DISCARD_DEFAULT_IMAGE:
// do nothing
source.skip(dataLength);
break;
case ANIMATED_KEEP_DEFAULT_IMAGE:
processor.processFrameImageData(source.slice(dataLength), PngChunkCode.IDAT, source.tell(), dataLength);
Expand All @@ -171,7 +180,7 @@ public void readImageDataChunk(PngSource source, int dataLength) throws PngExcep
processor.processDefaultImageData(source.slice(dataLength), PngChunkCode.IDAT, source.tell(), dataLength);
break;
}
source.skip(dataLength);
// source.skip(dataLength);
}

@Override
Expand Down Expand Up @@ -223,7 +232,6 @@ public void readFrameControlChunk(PngSource source, int dataLength) throws IOExc
}

processor.processFrameControl(frame);
//source.skip(dataLength);
}

//public abstract void setMainImageOp(PngMainImageOp op);
Expand Down Expand Up @@ -254,7 +262,7 @@ public void readFrameImageDataChunk(PngSource source, int dataLength) throws IOE
// // TODO: skip everything except the frame sequence number


source.skip(dataLength);
// source.skip(dataLength);
}

@Override
Expand Down
14 changes: 8 additions & 6 deletions api/src/main/java/net/ellerton/japng/reader/PngAtOnceSource.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,21 +83,23 @@ public int available() {
// return sourceDescription;
// }

@Override
public ByteArrayInputStream getBis() {
return bis;
}
// @Override
// public ByteArrayInputStream getBis() {
// return bis;
// }

@Override
public DataInputStream getDis() {
return dis;
}

@Override
public InputStream slice(int dataLength) {
public InputStream slice(int dataLength) throws IOException {
// The below would be fine but in this case we have the full byte stream anyway...
//return ByteStreams.limit(bis, dataLength);
return new ByteArrayInputStream(bytes, tell(), dataLength);
InputStream slice = new ByteArrayInputStream(bytes, tell(), dataLength);
this.skip(dataLength);
return slice;
}

public static PngAtOnceSource from(InputStream is) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ public static <ResultT> ResultT read(InputStream is, PngReader<ResultT> reader)
throw new PngException(PngConstants.ERROR_NOT_PNG, "Failed to read PNG signature");
}

PngAtOnceSource source = PngAtOnceSource.from(is);//, sourceName);
// PngAtOnceSource source = PngAtOnceSource.from(is);//, sourceName);
PngSource source = new PngStreamSource(is);
boolean finished = false;

while (!finished) {
Expand All @@ -62,7 +63,7 @@ public static <ResultT> ResultT read(InputStream is, PngReader<ResultT> reader)
finished = reader.readChunk(source, code, length);
}

if (source.available() > 0) {
if (source.available() > 0) { // Should trailing data after IEND always be error or can configure as warning?
throw new PngException(PngConstants.ERROR_EOF_EXPECTED, String.format("Completed IEND but %d byte(s) remain", source.available()));
}

Expand All @@ -71,7 +72,7 @@ public static <ResultT> ResultT read(InputStream is, PngReader<ResultT> reader)
return reader.getResult();

} catch (EOFException e) {
throw new PngException(PngConstants.ERROR_EOF, "Reading data and encountered unexpected EOF", e);
throw new PngException(PngConstants.ERROR_EOF, "Unexpected EOF", e);
} catch (IOException e) {
throw new PngException(PngConstants.ERROR_UNKNOWN_IO_FAILURE, e.getMessage(), e);
}
Expand Down
4 changes: 2 additions & 2 deletions api/src/main/java/net/ellerton/japng/reader/PngSource.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ public interface PngSource {

// String getSourceDescription();

ByteArrayInputStream getBis();
// ByteArrayInputStream getBis();

DataInputStream getDis();

InputStream slice(int dataLength);
InputStream slice(int dataLength) throws IOException;
}
88 changes: 88 additions & 0 deletions api/src/main/java/net/ellerton/japng/reader/PngStreamSource.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package net.ellerton.japng.reader;

import net.ellerton.japng.util.InputStreamSlice;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
* Created by aellerton on 13/06/2015.
*/
public class PngStreamSource implements PngSource {
final InputStream src;
//private final ByteArrayInputStream bis;
private final DataInputStream dis;

public PngStreamSource(InputStream src) {
this.src = src;
//this.bis = new ByteArrayInputStream(this.bytes); // never closed because nothing to do for ByteArrayInputStream
this.dis = new DataInputStream(this.src); // never closed because underlying stream doesn't need to be closed.

}

@Override
public int getLength() {
return 0; // TODO?
}

@Override
public boolean supportsByteAccess() {
return false;
}

@Override
public byte[] getBytes() throws IOException {
return null;
}

@Override
public byte readByte() throws IOException {
return dis.readByte();
}

@Override
public short readUnsignedShort() throws IOException {
return (short)dis.readUnsignedShort();
}

@Override
public int readInt() throws IOException {
return dis.readInt();
}

@Override
public long skip(int chunkLength) throws IOException {
return dis.skip(chunkLength);
}

@Override
public int tell() {
return 0; // TODO
}

@Override
public int available() {
try {
return dis.available(); // TODO: adequate?
} catch (IOException e) {
return 0; // TODO
}
}

// @Override
// public ByteArrayInputStream getBis() {
// return null;
// }

@Override
public DataInputStream getDis() {
return dis;
}

@Override
public InputStream slice(int dataLength) {
return new InputStreamSlice(src, dataLength);
}

}
Loading

0 comments on commit e6d5971

Please sign in to comment.