Skip to content

Commit

Permalink
Progressive images support #10642
Browse files Browse the repository at this point in the history
  • Loading branch information
rymsha committed Aug 15, 2024
1 parent dcefb15 commit 83e51f0
Show file tree
Hide file tree
Showing 12 changed files with 160 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,23 @@ public static byte[] writeImage( final BufferedImage image, final String format,

public static void writeImage( OutputStream out, final BufferedImage image, final String format, final int quality )
throws IOException
{
writeImage( out, image, format, quality, false );
}

public static void writeImage( OutputStream out, final BufferedImage image, final String format, final int quality,
boolean progressive )
throws IOException
{
final ImageWriter writer = getWriterByFormat( format );
try (ImageOutputStream output = new MemoryCacheImageOutputStream( out ))
{
writer.setOutput( output );
final ImageWriteParam params = writer.getDefaultWriteParam();
if ( progressive )
{
params.setProgressiveMode( ImageWriteParam.MODE_DEFAULT );
}
setCompressionQuality( params, quality );
writer.write( null, new IIOImage( image, null, null ), params );
}
Expand Down
1 change: 1 addition & 0 deletions modules/core/core-image/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
dependencies {
implementation project( ':core:core-api' )
implementation project( ':core:core-internal' )
implementation libs.jhlabs.filters
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@
int filters_maxTotal() default 25;

String memoryLimit() default "10%";

String progressive() default "jpeg";
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Iterator;
import java.util.Locale;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
Expand All @@ -30,6 +34,7 @@

import com.enonic.xp.content.ContentService;
import com.enonic.xp.core.impl.image.effect.ImageScaleFunction;
import com.enonic.xp.core.internal.SimpleCsvParser;
import com.enonic.xp.home.HomeDir;
import com.enonic.xp.image.Cropping;
import com.enonic.xp.image.ImageHelper;
Expand All @@ -54,6 +59,8 @@ public class ImageServiceImpl

private final MemoryCircuitBreaker circuitBreaker;

private final Set<String> progressiveOnFormats;

@Activate
public ImageServiceImpl( @Reference final ContentService contentService,
@Reference final ImageScaleFunctionBuilder imageScaleFunctionBuilder,
Expand All @@ -65,6 +72,13 @@ public ImageServiceImpl( @Reference final ContentService contentService,

this.circuitBreaker = new MemoryCircuitBreaker(
toMegaBytes( new MemoryLimitParser( Runtime.getRuntime()::maxMemory ).parse( config.memoryLimit() ) ) );

this.progressiveOnFormats = SimpleCsvParser.parseLine( config.progressive() )
.stream()
.map( String::trim )
.filter( Predicate.not( String::isEmpty ) )
.map( s -> s.toLowerCase( Locale.ROOT ) )
.collect( Collectors.toUnmodifiableSet() );
}

@Override
Expand Down Expand Up @@ -239,9 +253,13 @@ private boolean createImage( final ByteSource blob, final NormalizedImageParams
// but 0 quality in image service need to be retrofitted to "system default", otherwise JPEG with 0 quality
// is over-compressed and looks way different from system default compressed image.
final int writeImageQuality = readImageParams.getQuality() == 0 ? -1 : readImageParams.getQuality();

final boolean progressive =
progressiveOnFormats.stream().anyMatch( format -> format.equalsIgnoreCase( readImageParams.getFormat() ) );

try (OutputStream outputStream = sink.openBufferedStream())
{
ImageHelper.writeImage( outputStream, bufferedImage, readImageParams.getFormat(), writeImageQuality );
ImageHelper.writeImage( outputStream, bufferedImage, readImageParams.getFormat(), writeImageQuality, progressive );
}
LOG.debug( "Finish writing" );
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ private static Cropping normalizeCropping( final Cropping cropping )

private static String normalizeFormat( final ReadImageParams readImageParams )
{
// Existing should not set format parameter, but if it does all bets are off.
// Existing code should not set format parameter, but if it does all bets are off.
if ( readImageParams.getFormat() != null )
{
return readImageParams.getFormat();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import java.nio.file.Path;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.io.TempDir;

import com.google.common.io.ByteSource;
Expand Down Expand Up @@ -40,16 +42,23 @@ class ImageServiceImplTest

private byte[] imageDataOriginal;

ImageConfig imageConfig;

@BeforeEach
void setUp()
void setUp( final TestInfo info )
{
System.setProperty( "xp.home", temporaryFolder.toFile().getPath() );

contentId = ContentId.from( "contentId" );
binaryReference = BinaryReference.from( "binaryRef" );
contentService = mock( ContentService.class );

final ImageConfig imageConfig = mock( ImageConfig.class, invocation -> invocation.getMethod().getDefaultValue() );
imageConfig = mock( ImageConfig.class, invocation -> invocation.getMethod().getDefaultValue() );

if ( info.getTags().contains( "progressive_disabled" ) )
{
when( imageConfig.progressive() ).thenReturn( "" );
}

ImageFilterBuilderImpl imageFilterBuilder = new ImageFilterBuilderImpl();
imageFilterBuilder.activate( imageConfig );
Expand Down Expand Up @@ -107,6 +116,39 @@ void readImage_with_format()
assertArrayEquals( imageDataOriginal, imageData.read() );
}

@Test
public void readImage_jpeg_progressive_default()
throws IOException
{
mockOriginalImage( "original.png" );

final ReadImageParams readImageParams = ReadImageParams.newImageParams()
.contentId( contentId )
.binaryReference( binaryReference )
.mimeType( "image/jpeg" )
.build();

ByteSource imageData = imageService.readImage( readImageParams );
assertArrayEquals( readImage( "progressive.jpg" ), imageData.read() );
}

@Test
@Tag("progressive_disabled")
public void readImage_progressive_disabled()
throws IOException
{
mockOriginalImage( "original.png" );

final ReadImageParams readImageParams = ReadImageParams.newImageParams()
.contentId( contentId )
.binaryReference( binaryReference )
.mimeType( "image/jpeg" )
.build();

ByteSource imageData = imageService.readImage( readImageParams );
assertArrayEquals( readImage( "not_progressive.jpg" ), imageData.read() );
}

@Test
public void readImage_with_cache()
throws IOException
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.enonic.xp.core.internal;

import java.util.ArrayList;
import java.util.List;

public final class SimpleCsvParser
{
private SimpleCsvParser()
{
}

public static List<String> parseLine( final String line )
{
final List<String> fields = new ArrayList<>();
final StringBuilder field = new StringBuilder();
boolean inQuotes = false;

for ( int i = 0; i < line.length(); i++ )
{
char c = line.charAt( i );

if ( inQuotes )
{
if ( c == '\"' )
{
if ( i + 1 < line.length() && line.charAt( i + 1 ) == '\"' )
{
field.append( c );
i++;
}
else
{
inQuotes = false;
}
}
else
{
field.append( c );
}
}
else
{
if ( c == '\"' )
{
inQuotes = true;
}
else if ( c == ',' )
{
fields.add( field.toString() );
field.setLength( 0 );
}
else
{
field.append( c );
}
}
}

fields.add( field.toString() );

return fields;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.enonic.xp.core.internal;

import java.util.List;

import org.junit.jupiter.api.Test;

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

class SimpleCsvParserTest
{
@Test
void parseLine()
{
assertEquals( List.of( "a", "b", "c" ), SimpleCsvParser.parseLine( "a,b,c" ) );
assertEquals( List.of( "Cache-Control: no-store, private", " X-Instance: \"jupiter\"" ),
SimpleCsvParser.parseLine( "\"Cache-Control: no-store, private\", \"X-Instance: \"\"jupiter\"\"\"" ) );
}
}
1 change: 1 addition & 0 deletions modules/runtime/src/home/config/com.enonic.xp.image.cfg
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#scale.maxDimension = 8000
#filters.maxTotal = 25
#memoryLimit = 10%
#progressive = jpeg

0 comments on commit 83e51f0

Please sign in to comment.