package com.psddev.cms.tool.file; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.UUID; import java.util.stream.Collectors; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import com.psddev.cms.db.ImageCrop; import com.psddev.cms.db.ImageTag; import com.psddev.cms.db.ResizeOption; import com.psddev.cms.db.StandardImageSize; import com.psddev.cms.tool.FileContentType; import com.psddev.cms.tool.ToolPageContext; import com.psddev.cms.tool.page.ContentMetadata; import com.psddev.dari.db.ColorDistribution; import com.psddev.dari.db.ObjectField; import com.psddev.dari.db.State; import com.psddev.dari.util.CollectionUtils; import com.psddev.dari.util.DimsImageEditor; import com.psddev.dari.util.ImageEditor; import com.psddev.dari.util.JavaImageEditor; import com.psddev.dari.util.ObjectUtils; import com.psddev.dari.util.Settings; import com.psddev.dari.util.StorageItem; import com.psddev.dari.util.StringUtils; import com.psddev.dari.util.TypeReference; public class ImageFileType implements FileContentType { @Override public double getPriority(StorageItem storageItem) { String contentType = storageItem.getContentType(); if (StringUtils.isBlank(contentType) || !contentType.startsWith("image/")) { return DEFAULT_PRIORITY_LEVEL - 1; } return DEFAULT_PRIORITY_LEVEL; } @Override public void writePreview(ToolPageContext page, State state, StorageItem fieldValue) throws IOException, ServletException { HttpServletRequest request = page.getRequest(); ObjectField field = (ObjectField) request.getAttribute("field"); String fieldName; if (field != null) { fieldName = field.getInternalName(); } else { fieldName = page.param(String.class, "fieldName"); } String inputName = page.paramOrDefault(String.class, "inputName", (String) request.getAttribute("inputName")); String originalWidthName = inputName + ".originalWidth"; String actionName = inputName + ".action"; String cropsName = inputName + ".crops."; String brightnessName = inputName + ".brightness"; String contrastName = inputName + ".contrast"; String flipHName = inputName + ".flipH"; String flipVName = inputName + ".flipV"; String grayscaleName = inputName + ".grayscale"; String invertName = inputName + ".invert"; String rotateName = inputName + ".rotate"; String sepiaName = inputName + ".sepia"; String sharpenName = inputName + ".sharpen"; String blurName = inputName + ".blur"; String cropsFieldName = fieldName + ".crops"; String action = page.param(actionName); Map<String, Object> fieldValueMetadata = null; boolean isFormPost = request.getAttribute("isFormPost") != null ? (Boolean) request.getAttribute("isFormPost") : false; if (fieldValue != null && (!isFormPost || "keep".equals(action))) { fieldValueMetadata = fieldValue.getMetadata(); } if (fieldValueMetadata == null) { fieldValueMetadata = new LinkedHashMap<String, Object>(); } Map<String, Object> edits = (Map<String, Object>) fieldValueMetadata.get("cms.edits"); if (edits == null) { edits = new HashMap<String, Object>(); fieldValueMetadata.put("cms.edits", edits); } double brightness = ObjectUtils.to(double.class, edits.get("brightness")); double contrast = ObjectUtils.to(double.class, edits.get("contrast")); boolean flipH = ObjectUtils.to(boolean.class, edits.get("flipH")); boolean flipV = ObjectUtils.to(boolean.class, edits.get("flipV")); boolean grayscale = ObjectUtils.to(boolean.class, edits.get("grayscale")); boolean invert = ObjectUtils.to(boolean.class, edits.get("invert")); int rotate = ObjectUtils.to(int.class, edits.get("rotate")); boolean sepia = ObjectUtils.to(boolean.class, edits.get("sepia")); int sharpen = ObjectUtils.to(int.class, edits.get("sharpen")); List<String> blurs = new ArrayList<String>(); if (!ObjectUtils.isBlank(edits.get("blur"))) { Object blur = edits.get("blur"); if (blur instanceof String && ObjectUtils.to(String.class, blur).matches("(\\d+x){3}\\d+")) { blurs.add(ObjectUtils.to(String.class, blur)); } else if (blur instanceof List) { for (Object blurItem : (List) blur) { String blurValue = ObjectUtils.to(String.class, blurItem); if (blurValue.matches("(\\d+x){3}\\d+")) { blurs.add(blurValue); } } } } Map<String, ImageCrop> crops = ObjectUtils.to(new TypeReference<Map<String, ImageCrop>>() { }, fieldValueMetadata.get("cms.crops")); if (crops == null) { // for backward compatibility crops = ObjectUtils.to(new TypeReference<Map<String, ImageCrop>>() { }, state.getValue(cropsFieldName)); } if (crops == null) { crops = new HashMap<String, ImageCrop>(); } crops = new TreeMap<String, ImageCrop>(crops); Map<String, StandardImageSize> sizes = new HashMap<String, StandardImageSize>(); for (StandardImageSize size : StandardImageSize.findAll()) { String sizeId = size.getId().toString(); sizes.put(sizeId, size); if (crops.get(sizeId) == null) { crops.put(sizeId, new ImageCrop()); } } Map<String, Double> focusPoint = ObjectUtils.to(new TypeReference<Map<String, Double>>() { }, fieldValueMetadata.get("cms.focus")); if (focusPoint == null) { focusPoint = new HashMap<String, Double>(); } page.writeStart("div", "class", "imageEditor"); page.writeStart("div", "class", "imageEditor-aside"); page.writeStart("div", "class", "imageEditor-tools"); page.writeStart("h2"); page.writeHtml(page.localize(ImageFileType.class, "subtitle.tools")); page.writeEnd(); page.writeStart("ul"); if (state.as(ColorDistribution.Data.class).getDistribution() != null) { page.writeStart("li"); page.writeStart("a", "class", "icon icon-tint", "href", page.h(page.cmsUrl("/contentColors", "id", state.getId())), "target", "contentColors"); page.writeHtml(page.localize(ImageFileType.class, "action.viewContentColors")); page.writeEnd(); page.writeEnd(); } page.writeStart("li"); page.writeStart("a", "class", "action-preview", "href", fieldValue.getPublicUrl(), "target", "_blank"); page.writeHtml(page.localize(ImageFileType.class, "action.viewOriginal")); page.writeEnd(); page.writeEnd(); Map<String, Object> coreMetadata = fieldValueMetadata.entrySet().stream() .filter(entry -> !entry.getKey().startsWith("cms.")) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); if (!coreMetadata.isEmpty()) { page.writeStart("li"); // If object is new, post metadata. if (state.isNew()) { page.writeStart("form", "id", "contentMetadata", "method", "post", "action", page.cmsUrl(ContentMetadata.PATH), "target", "contentMetadata", "style", page.cssString("margin", 0)); page.writeElement("input", "type", "hidden", "name", "metadata", "value", ObjectUtils.toJson(coreMetadata)); page.writeStart("a", "class", "action-image-viewMetadata", "onclick", "$('#contentMetadata').submit();"); page.writeHtml(page.localize(ImageFileType.class, "action.viewMetadata")); page.writeEnd(); page.writeEnd(); } else { page.writeStart("a", "class", "action-image-viewMetadata", "href", page.cmsUrl(ContentMetadata.PATH, "id", state.getId(), "fieldName", fieldName), "target", "contentMetadata"); page.writeHtml(page.localize(ImageFileType.class, "action.viewMetadata")); page.writeEnd(); } page.writeEnd(); } if (!StandardImageSize.findAll().isEmpty()) { page.writeStart("li"); page.writeStart("a", "class", "icon icon-crop", "data-frame-post", "", "href", page.h(page.url("/contentImages", "data", ObjectUtils.toJson(fieldValue))), "target", "contentImages"); page.writeHtml(page.localize(ImageFileType.class, "action.viewResized")); page.writeEnd(); page.writeEnd(); } page.writeEnd(); page.writeEnd(); page.writeStart("div", "class", "imageEditor-edit"); page.writeStart("h2"); page.writeHtml(page.localize(ImageFileType.class, "subtitle.adjustments")); page.writeEnd(); boolean usingJavaImageEditor = ImageEditor.Static.getDefault() != null && (ImageEditor.Static.getDefault() instanceof JavaImageEditor); page.writeStart("table"); page.writeStart("tbody"); if (usingJavaImageEditor) { page.writeStart("tr"); page.writeStart("th"); page.writeHtml(page.localize(ImageFileType.class, "label.blur")); page.writeEnd(); page.writeStart("td"); page.writeStart("a", "class", "imageEditor-addBlurOverlay"); page.writeHtml(page.localize(ImageFileType.class, "action.addBlur")); page.writeEnd(); page.writeTag("br"); if (!ObjectUtils.isBlank(blurs)) { for (String blur : blurs) { page.writeTag("input", "type", "hidden", "name", page.h(blurName), "value", page.h(blur)); } } page.writeEnd(); page.writeEnd(); } // Brightness page.writeStart("tr"); page.writeStart("th"); page.writeHtml(page.localize(ImageFileType.class, "label.brightness")); page.writeEnd(); page.writeStart("td"); page.writeTag("input", "type", "range", "name", page.h(brightnessName), "value", page.h(brightness), "min", "-1.0", "max", "1.0", "step", "0.01"); page.writeEnd(); page.writeEnd(); // Contrast page.writeStart("tr"); page.writeStart("th"); page.writeHtml(page.localize(ImageFileType.class, "label.contrast")); page.writeEnd(); page.writeStart("td"); page.writeTag("input", "type", "range", "name", page.h(contrastName), "value", page.h(contrast), "min", "-1.0", "max", "1.0", "step", "0.01"); page.writeEnd(); page.writeEnd(); // Flip H page.writeStart("tr"); page.writeStart("th"); page.writeHtml(page.localize(ImageFileType.class, "label.flipH")); page.writeEnd(); page.writeStart("td"); page.writeTag("input", "type", "checkbox", "name", page.h(flipHName), "value", page.h("true"), flipH ? "checked" : "", ""); page.writeEnd(); page.writeEnd(); // Flip V page.writeStart("tr"); page.writeStart("th"); page.writeHtml(page.localize(ImageFileType.class, "label.flipV")); page.writeEnd(); page.writeStart("td"); page.writeTag("input", "type", "checkbox", "name", page.h(flipVName), "value", page.h("true"), flipV ? "checked" : "", ""); page.writeEnd(); page.writeEnd(); // Invert page.writeStart("tr"); page.writeStart("th"); page.writeHtml(page.localize(ImageFileType.class, "label.invert")); page.writeEnd(); page.writeStart("td"); page.writeTag("input", "type", "checkbox", "name", page.h(invertName), "value", page.h("true"), invert ? "checked" : "", ""); page.writeEnd(); page.writeEnd(); // Grayscale page.writeStart("tr"); page.writeStart("th"); page.writeHtml(page.localize(ImageFileType.class, "label.grayscale")); page.writeEnd(); page.writeStart("td"); page.writeTag("input", "type", "checkbox", "name", page.h(grayscaleName), "value", page.h("true"), grayscale ? "checked" : "", ""); page.writeEnd(); page.writeEnd(); // Rotate page.writeStart("tr"); page.writeStart("th"); page.writeHtml(page.localize(ImageFileType.class, "label.rotate")); page.writeEnd(); page.writeStart("td"); page.writeTag("input", "type", "range", "name", page.h(rotateName), "value", page.h(rotate), "min", "-90", "max", "90", "step", "90"); page.writeEnd(); page.writeEnd(); // Sepia page.writeStart("tr"); page.writeStart("th"); page.writeHtml(page.localize(ImageFileType.class, "label.sepia")); page.writeEnd(); page.writeStart("td"); page.writeTag("input", "type", "checkbox", "name", page.h(sepiaName), "value", page.h("true"), sepia ? "checked" : "", ""); page.writeEnd(); page.writeEnd(); if (usingJavaImageEditor) { // Sharpen page.writeStart("tr"); page.writeStart("th"); page.writeHtml(page.localize(ImageFileType.class, "label.sharpen")); page.writeEnd(); page.writeStart("td"); page.writeTag("input", "type", "range", "name", page.h(sharpenName), "value", page.h(sharpen), "min", "0", "max", "10", "step", "1"); page.writeEnd(); page.writeEnd(); } page.writeEnd(); page.writeEnd(); page.writeEnd(); ImageEditor defaultImageEditor = ImageEditor.Static.getDefault(); boolean centerCrop = !(defaultImageEditor instanceof DimsImageEditor) || ((DimsImageEditor) defaultImageEditor).isUseLegacyThumbnail(); if (!crops.isEmpty()) { page.writeStart("div", "class", "imageEditor-sizes"); page.writeStart("h2"); page.writeHtml(page.localize(ImageFileType.class, "subtitle.sizes")); page.writeEnd(); page.writeStart("table", "data-crop-center", page.h(centerCrop)); page.writeStart("tbody"); for (Map.Entry<String, ImageCrop> e : crops.entrySet()) { String cropId = e.getKey(); ImageCrop crop = e.getValue(); StandardImageSize size = sizes.get(cropId); if (size == null && ObjectUtils.to(UUID.class, cropId) != null) { continue; } if (size != null) { page.writeStart("tr", "data-size-name", page.h(size.getInternalName()), "data-size-independent", page.h(size.isIndependent()), "data-size-width", page.h(size.getWidth()), "data-size-height", page.h(size.getHeight())); page.writeStart("th"); page.write(page.h(size.getDisplayName())); page.writeEnd(); } else { page.writeStart("tr"); page.writeStart("th"); page.write(page.h(cropId)); page.writeEnd(); } // Crop X page.writeStart("td"); page.writeTag("input", "name", page.h(cropsName + cropId + ".x"), "type", "text", "value", crop.getX()); page.writeEnd(); // Crop Y page.writeStart("td"); page.writeTag("input", "name", page.h(cropsName + cropId + ".y"), "type", "text", "value", crop.getY()); page.writeEnd(); // Crop Width page.writeStart("td"); page.writeTag("input", "name", page.h(cropsName + cropId + ".width"), "type", "text", "value", crop.getWidth()); page.writeEnd(); // Crop Height page.writeStart("td"); page.writeTag("input", "name", page.h(cropsName + cropId + ".height"), "type", "text", "value", crop.getHeight()); page.writeEnd(); // Crop Texts page.writeStart("td"); page.writeTag("input", "name", page.h(cropsName + cropId + ".texts"), "type", "text", "value", page.h(crop.getTexts())); page.writeEnd(); // Crop Texts Sizes page.writeStart("td"); page.writeTag("input", "name", page.h(cropsName + cropId + ".textSizes"), "type", "text", "value", page.h(crop.getTextSizes())); page.writeEnd(); // Crop Texts Xs page.writeStart("td"); page.writeTag("input", "name", page.h(cropsName + cropId + ".textXs"), "type", "text", "value", crop.getTextXs()); page.writeEnd(); // Crop Texts Ys page.writeStart("td"); page.writeTag("input", "name", page.h(cropsName + cropId + ".textYs"), "type", "text", "value", crop.getTextYs()); page.writeEnd(); // Crop Texts Widths page.writeStart("td"); page.writeTag("input", "name", page.h(cropsName + cropId + ".textWidths"), "type", "text", "value", crop.getTextWidths()); page.writeEnd(); //end tr page.writeEnd(); } page.writeEnd(); page.writeEnd(); page.writeEnd(); } page.writeEnd(); page.writeStart("div", "class", "imageEditor-image"); String fieldValueUrl; String resizeScale = ""; if (ImageEditor.Static.getDefault() != null) { ImageTag.Builder imageTagBuilder = new ImageTag.Builder(fieldValue) .setPrivateUrl(true) .setWidth(1000) .setResizeOption(ResizeOption.ONLY_SHRINK_LARGER) .setEdits(false); Object originalWidthObject = ObjectUtils.firstNonBlank( CollectionUtils.getByPath(imageTagBuilder.getItem().getMetadata(), "image/originalWidth"), CollectionUtils.getByPath(imageTagBuilder.getItem().getMetadata(), "dims/originalWidth"), CollectionUtils.getByPath(imageTagBuilder.getItem().getMetadata(), "width"), page.param(String.class, originalWidthName) ); int originalWidth; if (originalWidthObject instanceof Number) { originalWidth = ((Number) originalWidthObject).intValue(); } else { originalWidth = ObjectUtils.to(double.class, originalWidthObject).intValue(); } if (originalWidth > 1000) { resizeScale = String.format("%.2f", (double) 1000 / originalWidth); } fieldValueUrl = imageTagBuilder.toUrl(); } else { fieldValueUrl = fieldValue.getPublicUrl(); } page.writeTag("img", "alt", "", "data-scale", resizeScale, "src", page.url("/misc/proxy.jsp", "url", fieldValueUrl, "hash", StringUtils.hex(StringUtils.hmacSha1(Settings.getSecret(), fieldValueUrl)))); page.writeTag("input", "type", "hidden", "name", page.h(inputName + ".focusX"), "value", page.h(focusPoint != null && focusPoint.containsKey("x") ? focusPoint.get("x") : "")); page.writeTag("input", "type", "hidden", "name", page.h(inputName + ".focusY"), "value", page.h(focusPoint != null && focusPoint.containsKey("y") ? focusPoint.get("y") : "")); page.writeEnd(); page.writeEnd(); } }