29 changed files with 444 additions and 289 deletions
@ -0,0 +1,29 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2023 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.common.data; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonInclude; |
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
@JsonInclude(JsonInclude.Include.NON_NULL) |
||||
|
public class ImageDescriptor { |
||||
|
private String mediaType; |
||||
|
private int width; |
||||
|
private int height; |
||||
|
private long size; |
||||
|
private ImageDescriptor previewDescriptor; |
||||
|
} |
||||
@ -1,96 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2023 The Thingsboard Authors |
|
||||
* |
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||
* you may not use this file except in compliance with the License. |
|
||||
* You may obtain a copy of the License at |
|
||||
* |
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||
* |
|
||||
* Unless required by applicable law or agreed to in writing, software |
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||
* See the License for the specific language governing permissions and |
|
||||
* limitations under the License. |
|
||||
*/ |
|
||||
package org.thingsboard.server.common.data.util; |
|
||||
|
|
||||
import lombok.AccessLevel; |
|
||||
import lombok.Data; |
|
||||
import lombok.NoArgsConstructor; |
|
||||
import org.springframework.util.MimeType; |
|
||||
import org.springframework.util.MimeTypeUtils; |
|
||||
|
|
||||
import javax.imageio.ImageIO; |
|
||||
import java.awt.image.BufferedImage; |
|
||||
import java.io.ByteArrayInputStream; |
|
||||
import java.io.ByteArrayOutputStream; |
|
||||
import java.io.IOException; |
|
||||
import java.util.Map; |
|
||||
|
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE) |
|
||||
public class ImageUtils { |
|
||||
|
|
||||
private static final Map<String, String> mediaTypeMappings = Map.of( |
|
||||
"jpeg", "jpg", |
|
||||
"svg+xml", "svg" |
|
||||
); |
|
||||
|
|
||||
public static String mediaTypeToFileExtension(String mimeType) { |
|
||||
String subtype = MimeTypeUtils.parseMimeType(mimeType).getSubtype(); |
|
||||
return mediaTypeMappings.getOrDefault(subtype, subtype); |
|
||||
} |
|
||||
|
|
||||
public static String fileExtensionToMediaType(String type, String extension) { |
|
||||
String subtype = mediaTypeMappings.entrySet().stream() |
|
||||
.filter(mapping -> mapping.getValue().equals(extension)) |
|
||||
.map(Map.Entry::getKey).findFirst().orElse(extension); |
|
||||
return new MimeType(type, subtype).toString(); |
|
||||
} |
|
||||
|
|
||||
public static ImageInfo processImage(byte[] imageData) throws IOException { |
|
||||
BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageData)); |
|
||||
ImageThumbnail thumbnail = getImageThumbnail(image, 250); |
|
||||
return new ImageInfo(image.getWidth(), image.getHeight(), thumbnail); |
|
||||
} |
|
||||
|
|
||||
private static ImageThumbnail getImageThumbnail(BufferedImage originalImage, int maxDimension) throws IOException { |
|
||||
int originalWidth = originalImage.getWidth(); |
|
||||
int originalHeight = originalImage.getHeight(); |
|
||||
int thumbnailWidth; |
|
||||
int thumbnailHeight; |
|
||||
if (originalWidth <= maxDimension && originalHeight <= maxDimension) { |
|
||||
thumbnailWidth = originalWidth; |
|
||||
thumbnailHeight = originalHeight; |
|
||||
} else { |
|
||||
double aspectRatio = (double) originalWidth / originalHeight; |
|
||||
if (originalWidth > originalHeight) { |
|
||||
thumbnailWidth = maxDimension; |
|
||||
thumbnailHeight = (int) (maxDimension / aspectRatio); |
|
||||
} else { |
|
||||
thumbnailWidth = (int) (maxDimension * aspectRatio); |
|
||||
thumbnailHeight = maxDimension; |
|
||||
} |
|
||||
} |
|
||||
BufferedImage thumbnail = new BufferedImage(thumbnailWidth, thumbnailHeight, BufferedImage.TYPE_INT_RGB); |
|
||||
thumbnail.getGraphics().drawImage(originalImage, 0, 0, thumbnailWidth, thumbnailHeight, null); |
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream(); |
|
||||
ImageIO.write(thumbnail, "ignored", os); |
|
||||
return new ImageThumbnail(thumbnail.getWidth(), thumbnail.getHeight(), os.toByteArray()); |
|
||||
} |
|
||||
|
|
||||
@Data |
|
||||
public static class ImageInfo { |
|
||||
private final int width; |
|
||||
private final int height; |
|
||||
private final ImageThumbnail thumbnail; |
|
||||
} |
|
||||
|
|
||||
@Data |
|
||||
public static class ImageThumbnail { |
|
||||
private final int width; |
|
||||
private final int height; |
|
||||
private final byte[] data; |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -0,0 +1,144 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2023 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.util; |
||||
|
|
||||
|
import lombok.AccessLevel; |
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
import org.apache.batik.transcoder.TranscoderInput; |
||||
|
import org.apache.batik.transcoder.TranscoderOutput; |
||||
|
import org.apache.batik.transcoder.image.PNGTranscoder; |
||||
|
import org.springframework.util.MimeType; |
||||
|
import org.springframework.util.MimeTypeUtils; |
||||
|
|
||||
|
import javax.imageio.ImageIO; |
||||
|
import java.awt.image.BufferedImage; |
||||
|
import java.io.ByteArrayInputStream; |
||||
|
import java.io.ByteArrayOutputStream; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE) |
||||
|
public class ImageUtils { |
||||
|
|
||||
|
private static final Map<String, String> mediaTypeMappings = Map.of( |
||||
|
"jpeg", "jpg", |
||||
|
"svg+xml", "svg" |
||||
|
); |
||||
|
|
||||
|
public static String mediaTypeToFileExtension(String mimeType) { |
||||
|
String subtype = MimeTypeUtils.parseMimeType(mimeType).getSubtype(); |
||||
|
return mediaTypeMappings.getOrDefault(subtype, subtype); |
||||
|
} |
||||
|
|
||||
|
public static String fileExtensionToMediaType(String type, String extension) { |
||||
|
String subtype = mediaTypeMappings.entrySet().stream() |
||||
|
.filter(mapping -> mapping.getValue().equals(extension)) |
||||
|
.map(Map.Entry::getKey).findFirst().orElse(extension); |
||||
|
return new MimeType(type, subtype).toString(); |
||||
|
} |
||||
|
|
||||
|
public static ProcessedImage processImage(byte[] data, String mediaType, int thumbnailMaxDimension) throws Exception { |
||||
|
if (mediaTypeToFileExtension(mediaType).equals("svg")) { |
||||
|
return processSvgImage(data, thumbnailMaxDimension); |
||||
|
} |
||||
|
|
||||
|
BufferedImage bufferedImage = ImageIO.read(new ByteArrayInputStream(data)); |
||||
|
ProcessedImage image = new ProcessedImage(); |
||||
|
image.setMediaType(mediaType); |
||||
|
image.setWidth(bufferedImage.getWidth()); |
||||
|
image.setHeight(bufferedImage.getHeight()); |
||||
|
image.setData(data); |
||||
|
image.setSize(data.length); |
||||
|
|
||||
|
ProcessedImage preview = new ProcessedImage(); |
||||
|
int[] thumbnailDimensions = getThumbnailDimensions(image.getWidth(), image.getHeight(), thumbnailMaxDimension); |
||||
|
preview.setWidth(thumbnailDimensions[0]); |
||||
|
preview.setHeight(thumbnailDimensions[1]); |
||||
|
|
||||
|
if (preview.getWidth() == image.getWidth() && preview.getHeight() == image.getHeight()) { |
||||
|
if (mediaType.equals("image/png")) { |
||||
|
preview.setMediaType(mediaType); |
||||
|
preview.setData(null); |
||||
|
preview.setSize(data.length); |
||||
|
image.setPreview(preview); |
||||
|
return image; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
BufferedImage thumbnail = new BufferedImage(preview.getWidth(), preview.getHeight(), BufferedImage.TYPE_INT_RGB); |
||||
|
thumbnail.getGraphics().drawImage(bufferedImage, 0, 0, preview.getWidth(), preview.getHeight(), null); |
||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream(); |
||||
|
ImageIO.write(thumbnail, "png", out); |
||||
|
|
||||
|
preview.setMediaType("image/png"); |
||||
|
preview.setData(out.toByteArray()); |
||||
|
preview.setSize(preview.getData().length); |
||||
|
image.setPreview(preview); |
||||
|
return image; |
||||
|
} |
||||
|
|
||||
|
public static ProcessedImage processSvgImage(byte[] data, int thumbnailMaxDimension) throws Exception { |
||||
|
ProcessedImage image = new ProcessedImage(); |
||||
|
image.setWidth(0); |
||||
|
image.setHeight(0); |
||||
|
image.setData(data); |
||||
|
image.setSize(data.length); |
||||
|
|
||||
|
PNGTranscoder transcoder = new PNGTranscoder(); |
||||
|
transcoder.addTranscodingHint(PNGTranscoder.KEY_MAX_WIDTH, (float) thumbnailMaxDimension); |
||||
|
transcoder.addTranscodingHint(PNGTranscoder.KEY_MAX_HEIGHT, (float) thumbnailMaxDimension); |
||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream(); |
||||
|
transcoder.transcode(new TranscoderInput(new ByteArrayInputStream(data)), new TranscoderOutput(out)); |
||||
|
byte[] pngThumbnail = out.toByteArray(); |
||||
|
|
||||
|
ProcessedImage preview = new ProcessedImage(); |
||||
|
preview.setWidth(thumbnailMaxDimension); |
||||
|
preview.setHeight(thumbnailMaxDimension); |
||||
|
preview.setMediaType("image/png"); |
||||
|
preview.setData(pngThumbnail); |
||||
|
preview.setSize(pngThumbnail.length); |
||||
|
image.setPreview(preview); |
||||
|
return image; |
||||
|
} |
||||
|
|
||||
|
private static int[] getThumbnailDimensions(int originalWidth, int originalHeight, int maxDimension) { |
||||
|
if (originalWidth <= maxDimension && originalHeight <= maxDimension) { |
||||
|
return new int[]{originalWidth, originalHeight}; |
||||
|
} |
||||
|
int thumbnailWidth; |
||||
|
int thumbnailHeight; |
||||
|
double aspectRatio = (double) originalWidth / originalHeight; |
||||
|
if (originalWidth > originalHeight) { |
||||
|
thumbnailWidth = maxDimension; |
||||
|
thumbnailHeight = (int) (maxDimension / aspectRatio); |
||||
|
} else { |
||||
|
thumbnailWidth = (int) (maxDimension * aspectRatio); |
||||
|
thumbnailHeight = maxDimension; |
||||
|
} |
||||
|
return new int[]{thumbnailWidth, thumbnailHeight}; |
||||
|
} |
||||
|
|
||||
|
@Data |
||||
|
public static class ProcessedImage { |
||||
|
private String mediaType; |
||||
|
private int width; |
||||
|
private int height; |
||||
|
private byte[] data; |
||||
|
private long size; |
||||
|
private ProcessedImage preview; |
||||
|
} |
||||
|
|
||||
|
} |
||||
Loading…
Reference in new issue