Created
August 22, 2022 14:31
-
-
Save RichardBradley/e7326ec777faccb9579ad4e0b0358f87 to your computer and use it in GitHub Desktop.
Demo code for https://stackoverflow.com/questions/73228364/is-there-a-way-to-incrementally-write-to-an-image-file-to-avoid-running-out-of-r
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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