1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package io.wcm.handler.mediasource.dam.impl.dynamicmedia;
21
22 import java.net.URLEncoder;
23 import java.nio.charset.StandardCharsets;
24
25 import org.apache.commons.lang3.StringUtils;
26 import org.jetbrains.annotations.NotNull;
27 import org.jetbrains.annotations.Nullable;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
30
31 import io.wcm.handler.media.CropDimension;
32 import io.wcm.handler.media.Dimension;
33 import io.wcm.handler.media.format.Ratio;
34 import io.wcm.handler.media.impl.ImageQualityPercentage;
35 import io.wcm.handler.mediasource.dam.impl.DamContext;
36 import io.wcm.wcm.commons.contenttype.ContentType;
37
38
39
40
41 public final class DynamicMediaPath {
42
43
44
45
46 @SuppressWarnings("java:S1075")
47 private static final String IMAGE_SERVER_PATH = "/is/image/";
48
49
50
51
52 @SuppressWarnings("java:S1075")
53 private static final String CONTENT_SERVER_PATH = "/is/content/";
54
55
56
57
58
59
60 public static final String DOWNLOAD_SUFFIX = "?cdh=attachment";
61
62 private static final Logger log = LoggerFactory.getLogger(DynamicMediaPath.class);
63
64 private DynamicMediaPath() {
65
66 }
67
68
69
70
71
72
73
74 public static @NotNull String buildContent(@NotNull DamContext damContext, boolean contentDispositionAttachment) {
75 StringBuilder result = new StringBuilder();
76 result.append(CONTENT_SERVER_PATH).append(encodeDynamicMediaObject(damContext));
77 if (contentDispositionAttachment) {
78 result.append(DOWNLOAD_SUFFIX);
79 }
80 return result.toString();
81 }
82
83
84
85
86
87
88 public static @NotNull String buildImage(@NotNull DamContext damContext) {
89 return IMAGE_SERVER_PATH + encodeDynamicMediaObject(damContext);
90 }
91
92
93
94
95
96
97
98
99 public static @Nullable String buildImage(@NotNull DamContext damContext, long width, long height) {
100 return buildImage(damContext, width, height, null, null);
101 }
102
103
104
105
106
107
108
109
110
111
112 public static @Nullable String buildImage(@NotNull DamContext damContext, long width, long height,
113 @Nullable CropDimension cropDimension, @Nullable Integer rotation) {
114 Dimension dimension = calcWidthHeight(damContext, width, height);
115
116 StringBuilder result = new StringBuilder();
117 result.append(IMAGE_SERVER_PATH).append(encodeDynamicMediaObject(damContext));
118
119
120 if (SmartCrop.canApply(cropDimension, rotation)) {
121
122 NamedDimension smartCropDef = SmartCrop.getDimensionForWidthHeight(damContext.getImageProfile(), width, height);
123 if (smartCropDef != null) {
124 if (damContext.isDynamicMediaValidateSmartCropRenditionSizes()
125 && !SmartCrop.isMatchingSize(damContext.getAsset(), damContext.getResourceResolver(), smartCropDef, width, height)) {
126
127 logResult(damContext, "<too small for " + width + "x" + height + ">");
128 return null;
129 }
130 result.append("%3A").append(smartCropDef.getName()).append("?");
131 appendWidthHeigtFormatQuality(result, dimension, damContext);
132 logResult(damContext, result);
133 return result.toString();
134 }
135 }
136
137 result.append("?");
138 if (cropDimension != null) {
139 result.append("crop=").append(cropDimension.getCropStringWidthHeight()).append("&");
140 }
141 if (rotation != null) {
142 result.append("rotate=").append(rotation).append("&");
143 }
144 appendWidthHeigtFormatQuality(result, dimension, damContext);
145 logResult(damContext, result);
146 return result.toString();
147 }
148
149 private static void appendWidthHeigtFormatQuality(@NotNull StringBuilder result, @NotNull Dimension dimension, @NotNull DamContext damContext) {
150 result.append("wid=").append(dimension.getWidth())
151 .append("&hei=").append(dimension.getHeight())
152
153 .append("&fit=stretch");
154
155 if (mayHaveAlphaChannel(damContext)) {
156 applyFmt(result, damContext.getDynamicMediaDefaultFmtAlpha());
157 }
158 else {
159 applyFmt(result, damContext.getDynamicMediaDefaultFmt());
160 }
161 if (damContext.isDynamicMediaSetImageQuality() && !isLosslessImageFormat(damContext)) {
162
163 result.append("&qlt=").append(ImageQualityPercentage.getAsInteger(damContext.getMediaArgs(), damContext.getMediaHandlerConfig()));
164 }
165 }
166
167 private static void applyFmt(@NotNull StringBuilder result, @NotNull String fmt) {
168 if (StringUtils.isNotBlank(fmt)) {
169 result.append("&fmt=").append(fmt);
170 }
171 }
172
173 private static void logResult(@NotNull DamContext damContext, @NotNull CharSequence result) {
174 if (log.isTraceEnabled()) {
175 log.trace("Build dynamic media path for {}: {}", damContext.getAsset().getPath(), result);
176 }
177 }
178
179
180
181
182
183
184
185
186 private static Dimension calcWidthHeight(@NotNull DamContext damContext, long width, long height) {
187 Dimension sizeLimit = damContext.getDynamicMediaImageSizeLimit();
188 if (width > sizeLimit.getWidth()) {
189 double ratio = Ratio.get(width, height);
190 long newWidth = sizeLimit.getWidth();
191 long newHeight = Math.round(newWidth / ratio);
192 return calcWidthHeight(damContext, newWidth, newHeight);
193 }
194 if (height > sizeLimit.getHeight()) {
195 double ratio = Ratio.get(width, height);
196 long newHeight = sizeLimit.getHeight();
197 long newWidth = Math.round(newHeight * ratio);
198 return new Dimension(newWidth, newHeight);
199 }
200 return new Dimension(width, height);
201 }
202
203
204
205
206
207
208 private static String encodeDynamicMediaObject(@NotNull DamContext damContext) {
209 String[] pathParts = StringUtils.split(damContext.getDynamicMediaObject(), "/");
210 for (int i = 0; i < pathParts.length; i++) {
211 pathParts[i] = URLEncoder.encode(pathParts[i], StandardCharsets.UTF_8);
212
213 pathParts[i] = StringUtils.replace(pathParts[i], "+", "%20");
214 }
215 return StringUtils.join(pathParts, "/");
216 }
217
218
219
220
221
222
223 private static boolean mayHaveAlphaChannel(@NotNull DamContext damContext) {
224 String mimeType = damContext.getAsset().getMimeType();
225 return StringUtils.equalsAny(mimeType, ContentType.PNG, ContentType.WEBP);
226 }
227
228
229
230
231
232
233 private static boolean isLosslessImageFormat(@NotNull DamContext damContext) {
234 String mimeType = damContext.getAsset().getMimeType();
235 return StringUtils.equals(mimeType, ContentType.PNG);
236 }
237
238 }