Skip to content

Instantly share code, notes, and snippets.

@RichardBradley
Created August 22, 2022 14:31
Show Gist options
  • Save RichardBradley/e7326ec777faccb9579ad4e0b0358f87 to your computer and use it in GitHub Desktop.
Save RichardBradley/e7326ec777faccb9579ad4e0b0358f87 to your computer and use it in GitHub Desktop.
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.*;
import java.io.File;
import java.util.Vector;
import static com.google.common.base.Preconditions.checkState;
public class Q73228364 {
public static void main(String[] args) throws Exception {
File file = new File("test.png");
if (!ImageIO.write(streamingImage(), "png", file)) {
throw new IllegalStateException("Couldn't write");
}
System.out.println("Written to: " + file.getAbsolutePath());
}
private static RenderedImage streamingImage() {
int WIDTH = 1000;
int HEIGHT = 1000;
return new RenderedImage() {
final ColorModel colorModel = ColorModel.getRGBdefault();
final SampleModel sampleModel = getColorModel().createCompatibleSampleModel(WIDTH, HEIGHT);
/**
* A RenderedImage contains multiple "Tiles" each of which is a "Raster",
* i.e. a fully rendered image portion.
*
* We might think that we could coax java.awt.image to write in a streaming style
* by dividing the image into lots of Tiles.
* However the PNG encoder will request the image one scanline at a time, regardless
* of the Tile settings of the Image.
* See com/sun/imageio/plugins/png/PNGImageWriter.java:818
* This may in fact be a bug in PNGImageWriter, but no-one has noticed because no-one
* writes images in a streaming style in real world use.
*/
@Override
public int getNumXTiles() {
return 1;
}
@Override
public int getNumYTiles() {
return 1;
}
@Override
public int getTileWidth() {
return WIDTH;
}
@Override
public int getTileHeight() {
return HEIGHT;
}
@Override
public Vector<RenderedImage> getSources() {
throw new UnsupportedOperationException();
}
@Override
public Object getProperty(String name) {
throw new UnsupportedOperationException();
}
@Override
public String[] getPropertyNames() {
return new String[0];
}
@Override
public ColorModel getColorModel() {
return colorModel;
}
@Override
public SampleModel getSampleModel() {
return sampleModel;
}
@Override
public int getWidth() {
return WIDTH;
}
@Override
public int getHeight() {
return HEIGHT;
}
@Override
public int getMinX() {
return 0;
}
@Override
public int getMinY() {
return 0;
}
@Override
public int getMinTileX() {
return 0;
}
@Override
public int getMinTileY() {
return 0;
}
@Override
public int getTileGridXOffset() {
return 0;
}
@Override
public int getTileGridYOffset() {
return 0;
}
@Override
public Raster getTile(int tileX, int tileY) {
throw new UnsupportedOperationException();
}
@Override
public Raster getData() {
throw new UnsupportedOperationException();
}
@Override
public Raster getData(Rectangle rect) {
checkState(rect.height == 1);
checkState(rect.width == WIDTH);
int y = rect.y;
System.gc();
log("Starting line %s mem usage = %s",
y,
Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory());
// Create a data buffer of this single scan line
// the PNG encoder supplied by java.awt.image cannot stream any smaller increments
SampleModel rowSM = getColorModel().createCompatibleSampleModel(rect.width, rect.height);
WritableRaster raster = Raster.createWritableRaster(rowSM, new Point(0, y));
for (int x = 0; x < WIDTH; x++) {
// Simluate generating the image pixel-by-pixel
raster.setPixel(x, y, generatePixel(x, y));
}
// A Raster has been allocated above; the logs will show that it can be GC'd
// after this line has been written
System.gc();
log("Generated line %s mem usage = %s",
y,
Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory());
return raster;
}
@Override
public WritableRaster copyData(WritableRaster raster) {
throw new UnsupportedOperationException();
}
};
}
private static void log(String m, Object... args) {
System.out.println(String.format(m, args));
}
private static int[] generatePixel(int x, int y) {
int v = floatToRGBVal(Math.sin(x / 100.0 + y / 200.0));
return new int[]{v, v, v, 255};
}
private static int floatToRGBVal(double d) {
return Math.min(255, Math.max(0,
(int) (128 + d * 256)));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment