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