Skip to content

Commit

Permalink
Refactor image width/height ratio processing
Browse files Browse the repository at this point in the history
 - Defer height calculation to image generation instead of bundle processing
 - Make ratio optional
 - Add option to overwrite ratio in imageSize
  • Loading branch information
eschleb committed Nov 7, 2023
1 parent b2730af commit 6d3fd71
Show file tree
Hide file tree
Showing 19 changed files with 279 additions and 129 deletions.
73 changes: 67 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,19 @@ Magnolia image generator that uses json definitions to generate images.
{
"bundle": "16_7-stage",
"description": "ratio 16:7 bundle for stage image, VP S and M: 12cols wide, VP L: 12cols + container padding (breaking out of grid)",
"ratio": 2.285714285714286,
"ratio": "16:7",
"imageSizes": [
{
"width": 720,
"id": "720w"
"media": "720w"
},
{
"width": 1280,
"id": "1280w"
"media": "1280w"
},
{
"width": 2880,
"id": "2880w"
"media": "2880w"
}
],
"customRenditions": [
Expand All @@ -43,7 +43,68 @@ Magnolia image generator that uses json definitions to generate images.
]
```


## Bundle schema
```json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "bundle",
"type": "array",
"items": {
"type": "object",
"properties": {
"bundle": {
"type": "string",
"description": "bundle name"
},
"ratio": {
"type": "string",
"description": "e.g. '16:9'. If not specified here or in imageSize, the original image ratio will be preserved"
},
"imageSizes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"width": {
"type": "integer"
},
"media": {
"type": "string",
"description": "The media size for the image (e.g. '2880w')"
},
"ratio": {
"type": "string",
"description": "e.g. '16:9'. If not specified here or in the bundle, the original image ratio will be preserved"
}
},
"required": [ "width", "media" ]
}
},
"customRenditions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"width": {
"type": "integer"
},
"id": {
"type": "string",
"description": "identifier"
},
"ratio": {
"type": "string",
"description": "e.g. '16:9'"
}
},
"required": [ "width", "id" ]
}
}
},
"required": [ "bundle", "imageSizes", "customRenditions"]
}
}
```
## Custom image operations
It is possible to implement custom image operations by extending and binding your own instances:

Expand Down Expand Up @@ -78,7 +139,7 @@ public class CustomDynamicImageParameter extends DynamicImageParameter {
public Map<String, String> toMap() {
return Stream.concat(
super.toMap().entrySet().stream(),
Map.of(Factory.TRIGGER_CUSTOM_OPERATION_PARAM, isTriggerCustomOperation())
Map.of(Factory.TRIGGER_CUSTOM_OPERATION_PARAM, isTriggerCustomOperation()).entrySet().stream()
).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import info.magnolia.dam.api.Asset;
import info.magnolia.dam.templating.functions.DamTemplatingFunctions;

import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
Expand Down Expand Up @@ -42,7 +43,7 @@ public Optional<FlexibleParameter> parse(final HttpServletRequest request) {
flexibleParameterFactory.create(asset, key -> getParameter(uri, key))
)
.filter(parameter ->
isSizeValid(parameter.getWidth(), parameter.getHeight())
isSizeValid(parameter.getWidth(), parameter.getRatio().orElse(null))
);
}

Expand All @@ -62,7 +63,7 @@ private Optional<Asset> getAsset(final String uri) {
.map(damTemplatingFunctions::getAsset);
}

private boolean isSizeValid(final int width, final int height) {
private boolean isSizeValid(final int width, @Nullable final String ratio) {
return bundlesProvider.get().stream()
.flatMap(bundle ->
Stream.concat(
Expand All @@ -71,7 +72,7 @@ private boolean isSizeValid(final int width, final int height) {
)
)
.anyMatch(size ->
Objects.equals(size.getWidth(), width) && Objects.equals(size.getHeight(), height)
Objects.equals(size.getWidth(), width) && Objects.equals(size.getRatio().orElse(null), ratio)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public String getCachePath(
return "/" + generator.getName() + "/"
+ Text.escapeIllegalJcrChars(parameter.getItemKey().asString()) + "/"
+ parameter.getWidth() + "/"
+ parameter.getHeight() + "/"
+ parameter.getRatio().map(Text::escapeIllegalJcrChars).orElse("keepRatio") + "/"
+ parameter.getDynamicImageParameter().hashCode();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,25 @@
import com.merkle.oss.magnolia.imaging.flexible.generator.operation.FromFlexibleParameter;
import com.merkle.oss.magnolia.imaging.flexible.model.DynamicImageParameter;
import com.merkle.oss.magnolia.imaging.flexible.model.FlexibleParameter;
import com.merkle.oss.magnolia.imaging.flexible.model.bundle.RatioParser;
import info.magnolia.imaging.ParameterProvider;
import info.magnolia.imaging.operations.ImageOperationChain;
import info.magnolia.imaging.operations.cropresize.AutoCropAndResize;
import info.magnolia.imaging.operations.cropresize.BoundedResize;
import info.magnolia.imaging.operations.cropresize.resizers.MultiStepResizer;

import javax.inject.Inject;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Optional;

public class ImageOperationProvider {
private final RatioParser ratioParser;

@Inject
public ImageOperationProvider(final RatioParser ratioParser) {
this.ratioParser = ratioParser;
}

public ImageOperationChain<ParameterProvider<FlexibleParameter>> get(final FlexibleParameter parameter) {
final ImageOperationChain<ParameterProvider<FlexibleParameter>> chain = new ImageOperationChain<>();
Expand All @@ -19,15 +31,25 @@ public ImageOperationChain<ParameterProvider<FlexibleParameter>> get(final Flexi
final AutoCropAndResize resize = new AutoCropAndResize();
resize.setResizer(new MultiStepResizer());
resize.setTargetWidth(parameter.getWidth());
resize.setTargetHeight(parameter.getHeight());
calculateHeight(parameter).ifPresent(resize::setTargetHeight);
chain.addOperation(resize);
} else {
final BoundedResize resize = new BoundedResize();
resize.setResizer(new MultiStepResizer());
resize.setMaxWidth(parameter.getWidth());
resize.setMaxHeight(parameter.getHeight());
resize.setMaxHeight(calculateHeight(parameter).orElse(Integer.MAX_VALUE));
chain.addOperation(resize);
}
return chain;
}

protected Optional<Integer> calculateHeight(final FlexibleParameter parameter) {
return parameter.getRatio().flatMap(ratioParser::parse).map(ratio ->
calculateHeight(parameter.getWidth(), ratio)
);
}

private int calculateHeight(final int width, final double ratio) {
return BigDecimal.valueOf(width / ratio).setScale(0, RoundingMode.UP).intValue();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ private List<ImageModel.Rendition> getSrcSet(final ProcessedBundle bundle, final
return bundle.getImageSizes()
.stream()
.map(size ->
new ImageModel.Rendition(size.getId(), size.getWidth(), size.getHeight(), getUrl(size, asset, dynamicImageParameter))
new ImageModel.Rendition(size.getId(), getUrl(size, asset, dynamicImageParameter))
)
.collect(Collectors.toList());
}
Expand All @@ -92,7 +92,7 @@ private Map<String, String> getCustomRenditions(final ProcessedBundle bundle, fi
}

private String getUrl(final ProcessedBundle.ImageSize size, final Asset asset, @Nullable final DynamicImageParameter dynamicImageParameter) {
final FlexibleParameter parameter = new FlexibleParameter(dynamicImageParameter, size.getWidth(), size.getHeight(), asset);
final FlexibleParameter parameter = new FlexibleParameter(dynamicImageParameter, size.getRatio().orElse(null), size.getWidth(), asset);
return flexibleImageUriFactory.create(parameter).toString();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,19 @@
public class FlexibleParameter extends AssetDecorator {
@Nullable
private final DynamicImageParameter dynamicImageParameter;
@Nullable
private final String ratio;
private final int width;
private final int height;

public FlexibleParameter(
@Nullable final DynamicImageParameter dynamicImageParameter,
@Nullable final String ratio,
final int width,
final int height,
final Asset asset
) {
super(asset);
this.ratio = ratio;
this.width = width;
this.height = height;
this.dynamicImageParameter = dynamicImageParameter;
}

Expand All @@ -39,45 +40,36 @@ public int getWidth() {
return width;
}

public int getHeight() {
return height;
public Optional<String> getRatio() {
return Optional.ofNullable(ratio);
}

public Map<String, String> toMap() {
return Stream.concat(
return Stream.of(
getDynamicImageParameter().stream().map(DynamicImageParameter::toMap).map(Map::entrySet).flatMap(Collection::stream),
getRatio().stream().map(ratio -> Map.entry(Factory.RATIO_PARAM, ratio)),
Map.of(
Factory.WIDTH_PARAM, String.valueOf(getWidth()),
Factory.HEIGHT_PARAM, String.valueOf(getHeight())
Factory.WIDTH_PARAM, String.valueOf(getWidth())
).entrySet().stream()
).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (dynamicImageParam, flexibleParam) -> flexibleParam));
).flatMap(Function.identity()).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (dynamicImageParam, flexibleParam) -> flexibleParam));
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FlexibleParameter that = (FlexibleParameter) o;
return width == that.width && height == that.height && Objects.equals(dynamicImageParameter, that.dynamicImageParameter);
return width == that.width && Objects.equals(dynamicImageParameter, that.dynamicImageParameter) && Objects.equals(ratio, that.ratio);
}

@Override
public int hashCode() {
return Objects.hash(dynamicImageParameter, width, height);
}

@Override
public String toString() {
return "FlexibleParameter{" +
"imageParameter=" + dynamicImageParameter +
", width=" + width +
", height=" + height +
'}';
return Objects.hash(dynamicImageParameter, ratio, width);
}

public static class Factory {
public static final String WIDTH_PARAM = "width";
public static final String HEIGHT_PARAM = "height";
public static final String RATIO_PARAM = "ratio";

private final DynamicImageParameter.Factory dynamicImageParameterFactory;

Expand All @@ -87,14 +79,12 @@ public Factory(final DynamicImageParameter.Factory dynamicImageParameterFactory)
}

public Optional<FlexibleParameter> create(final Asset asset, final Function<String, Optional<String>> parameterProvider) {
return parameterProvider.apply(WIDTH_PARAM).map(Integer::parseInt).flatMap(width ->
parameterProvider.apply(HEIGHT_PARAM).map(Integer::parseInt).map(height ->
new FlexibleParameter(
dynamicImageParameterFactory.create(parameterProvider).orElse(null),
width,
height,
asset
)
return parameterProvider.apply(WIDTH_PARAM).map(Integer::parseInt).map(width ->
new FlexibleParameter(
dynamicImageParameterFactory.create(parameterProvider).orElse(null),
parameterProvider.apply(RATIO_PARAM).orElse(null),
width,
asset
)
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,34 +86,20 @@ public String toString() {

public static class Rendition {
private final String media;
private final Integer width;
private final Integer height;
private final String src;

public Rendition(
final String media,
final Integer width,
final Integer height,
final String src
) {
this.media = media;
this.width = width;
this.height = height;
this.src = src;
}

public String getMedia() {
return media;
}

public Integer getWidth() {
return width;
}

public Integer getHeight() {
return height;
}

public String getSrc() {
return src;
}
Expand All @@ -123,20 +109,18 @@ public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Rendition rendition = (Rendition) o;
return Objects.equals(media, rendition.media) && Objects.equals(width, rendition.width) && Objects.equals(height, rendition.height) && Objects.equals(src, rendition.src);
return Objects.equals(media, rendition.media) && Objects.equals(src, rendition.src);
}

@Override
public int hashCode() {
return Objects.hash(media, width, height, src);
return Objects.hash(media, src);
}

@Override
public String toString() {
return "Rendition{" +
"media='" + media + '\'' +
", width=" + width +
", height=" + height +
", src='" + src + '\'' +
'}';
}
Expand Down
Loading

0 comments on commit 6d3fd71

Please sign in to comment.