Skip to content

Commit

Permalink
imagePlaceholder() creates objects that remain in memory #10332
Browse files Browse the repository at this point in the history
  • Loading branch information
rymsha committed Nov 16, 2023
1 parent 7b7d215 commit bcfc761
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Base64;
import java.util.Iterator;

import javax.imageio.IIOImage;
Expand All @@ -31,19 +30,7 @@ private ImageHelper()

public static String createImagePlaceholder( final int width, final int height )
{
try
{
final BufferedImage image = createImage( width, height, true );
final ByteArrayOutputStream out = new ByteArrayOutputStream();
writeImage( out, image, "png", 0 );
final byte[] bytes = out.toByteArray();

return "data:image/png;base64," + Base64.getEncoder().encodeToString( bytes );
}
catch ( final IOException e )
{
throw Exceptions.newRuntime( "Failed to create image placeholder" ).withCause( e );
}
return new ImagePlaceholderFactory( width, height ).create();
}

@Deprecated
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package com.enonic.xp.image;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.zip.CRC32;
import java.util.zip.CheckedOutputStream;
import java.util.zip.DeflaterOutputStream;

final class ImagePlaceholderFactory
{
private static final byte[] PNG_MAGIC = {(byte) 137, 80, 78, 71, 13, 10, 26, 10};

private static final byte[] IDAT_TYPE_BYTES = "IDAT".getBytes( StandardCharsets.ISO_8859_1 );

private static final byte[] IHDR_TYPE_BYTES = "IHDR".getBytes( StandardCharsets.ISO_8859_1 );

private static final byte[] IHDR_CONST_PART = {(byte) 8, 6, 0, 0, 0};

private static final byte[] IEND = {(byte) 0, 0, 0, 0, 73, 69, 78, 68, -82, 66, 96, -126};

private static final byte[] PREFIX = "data:image/png;base64,".getBytes( StandardCharsets.ISO_8859_1 );

private final int width;

private final int height;

ImagePlaceholderFactory( final int width, final int height )
{
this.width = width;
this.height = height;
}

public String create()
{
ByteArrayOutputStream output = new ByteArrayOutputStream();
try
{
output.write( PREFIX );
try (var base64Stream = Base64.getEncoder().wrap( output ))
{
base64Stream.write( PNG_MAGIC );
writeChunk( base64Stream, IHDR_TYPE_BYTES, createIhdr() );
writeChunk( base64Stream, IDAT_TYPE_BYTES, createIdat() );
base64Stream.write( IEND );
}
}
catch ( IOException e )

Check warning on line 51 in modules/core/core-api/src/main/java/com/enonic/xp/image/ImagePlaceholderFactory.java

View check run for this annotation

Codecov / codecov/patch

modules/core/core-api/src/main/java/com/enonic/xp/image/ImagePlaceholderFactory.java#L51

Added line #L51 was not covered by tests
{
throw new UncheckedIOException( e );

Check warning on line 53 in modules/core/core-api/src/main/java/com/enonic/xp/image/ImagePlaceholderFactory.java

View check run for this annotation

Codecov / codecov/patch

modules/core/core-api/src/main/java/com/enonic/xp/image/ImagePlaceholderFactory.java#L53

Added line #L53 was not covered by tests
}

return output.toString( StandardCharsets.ISO_8859_1 );
}

private byte[] createIhdr()
throws IOException
{
ByteArrayOutputStream ihdr = new ByteArrayOutputStream();
writeBigEndianInt( ihdr, width );
writeBigEndianInt( ihdr, height );
ihdr.write( IHDR_CONST_PART );
return ihdr.toByteArray();
}

private byte[] createIdat()
throws IOException
{
final ByteArrayOutputStream idat = new ByteArrayOutputStream();

final int bufferSize = 512; // DeflaterOutputStream uses this size as a default buffer size
byte[] buffer = new byte[bufferSize];
int totalBytes = width * height * 5; // 5 bytes per pixel (RGBA)
try (DeflaterOutputStream defStream = new DeflaterOutputStream( idat ))
{
for ( int i = 0; i < totalBytes; i += bufferSize )
{
defStream.write( buffer, 0, Math.min( bufferSize, totalBytes - i ) );
}
}
return idat.toByteArray();
}

private void writeChunk( final OutputStream outputStream, final byte[] typeBytes, final byte[] data )
throws IOException
{
CheckedOutputStream crcStream = new CheckedOutputStream( outputStream, new CRC32() );
writeBigEndianInt( outputStream, data.length );
crcStream.write( typeBytes );
crcStream.write( data );
writeBigEndianInt( outputStream, (int) crcStream.getChecksum().getValue() );
}

private static void writeBigEndianInt( final OutputStream stream, final int value )
throws IOException
{
stream.write( ( value >>> 24 ) & 0xFF );
stream.write( ( value >>> 16 ) & 0xFF );
stream.write( ( value >>> 8 ) & 0xFF );
stream.write( value & 0xFF );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,14 @@
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

class ImageHelperTest
{
@Test
void createImagePlaceholder()
{
final String str = ImageHelper.createImagePlaceholder( 2, 2 );
assertNotNull( str );
assertEquals( "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAC0lEQVR42mNgQAcAABIAAeRVjecAAAAASUVORK5CYII=",
final String str = ImageHelper.createImagePlaceholder( 2, 3 );
assertEquals( "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAADCAYAAAC56t6BAAAAC0lEQVR4nGNgwAcAAB4AAfb96ZYAAAAASUVORK5CYII=",
str );
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ var url = portalLib.imagePlaceholder({

// BEGIN
// URL returned.
var expected = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAYCAYAAACbU/80AAAAGUlEQVR42u3BAQEAAACCIP+vbkhAAQAA7wYMGAAB93LuRQAAAABJRU5ErkJggg==';
var expected = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAYCAYAAACbU/80AAAAGklEQVR4nO3BAQ0AAADCoPdPbQ8HFAAAAPwbDwAAAQUUSzEAAAAASUVORK5CYII=';
// END

assert.assertEquals(expected, url);
2 changes: 1 addition & 1 deletion modules/lib/lib-portal/src/test/resources/test/url-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ exports.imagePlaceholderTest = function () {
});

assert.assertEquals(
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAKCAYAAADVTVykAAAAEklEQVR42mNgGAWjYBSMgpEOAAUKAAEVKR6qAAAAAElFTkSuQmCC',
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAKCAYAAADVTVykAAAAFUlEQVR4nGNgGAWjYBSMglEwCkgHAAZAAAEAAhBgAAAAAElFTkSuQmCC',
result);
return true;
};
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ protected void setupFunction()
public void testExecute()
{
final Object result = execute( "imagePlaceholder", "width=2", "height=2" );
assertEquals( "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAC0lEQVR42mNgQAcAABIAAeRVjecAAAAASUVORK5CYII=",
assertEquals( "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAC0lEQVR4nGNgwAQAABQAAX3+Hu4AAAAASUVORK5CYII=",
result );
}

Expand Down

0 comments on commit bcfc761

Please sign in to comment.