blob: ca3e581dfd739adedd867689b9675ba0da3b93cd [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* 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 com.intellij.codeInsight.preview;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFileSystemItem;
import com.intellij.psi.PsiReference;
import com.intellij.reference.SoftReference;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
/**
* @author spleaner
*/
public class ImagePreviewComponent extends JPanel implements PreviewHintComponent {
private static final Key<Long> TIMESTAMP_KEY = Key.create("Image.timeStamp");
private static final Key<SoftReference<BufferedImage>> BUFFERED_IMAGE_REF_KEY = Key.create("Image.bufferedImage");
private static final Key<String> FORMAT_KEY = Key.create("Image.format");
private static final List<String> supportedExtensions = Arrays.asList(ImageIO.getReaderFormatNames());
@NotNull
private final BufferedImage myImage;
/**
* @param image buffered image
* @param imageFileSize File length in bytes.
*/
private ImagePreviewComponent(@NotNull final BufferedImage image, final long imageFileSize) {
setLayout(new BorderLayout());
myImage = image;
add(new ImageComp(), BorderLayout.CENTER);
add(createLabel(image, imageFileSize), BorderLayout.SOUTH);
setBackground(UIUtil.getToolTipBackground());
setBorder(BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(Color.black), BorderFactory.createEmptyBorder(5, 5, 5, 5)));
}
@Override
@TestOnly
public boolean isEqualTo(@Nullable PreviewHintComponent other) {
if (!(other instanceof ImagePreviewComponent)) {
return false;
}
ImagePreviewComponent otherPreview = (ImagePreviewComponent)other;
if (myImage.getWidth() == otherPreview.myImage.getWidth() && myImage.getHeight() == otherPreview.myImage.getHeight()) {
for (int x = 0; x < myImage.getWidth(); x++) {
for (int y = 0; y < myImage.getHeight(); y++) {
if (myImage.getRGB(x, y) != otherPreview.myImage.getRGB(x, y)) {
return false;
}
}
}
return true;
}
return false;
}
@NotNull
private static JLabel createLabel(@NotNull final BufferedImage image, long imageFileSize) {
final int width = image.getWidth();
final int height = image.getHeight();
final ColorModel colorModel = image.getColorModel();
final int i = colorModel.getPixelSize();
return new JLabel(String.format("%dx%d, %dbpp, %s", width, height, i, StringUtil.formatFileSize(imageFileSize)));
}
@SuppressWarnings({"AutoUnboxing"})
private static boolean refresh(@NotNull VirtualFile file) throws IOException {
Long loadedTimeStamp = file.getUserData(TIMESTAMP_KEY);
SoftReference<BufferedImage> imageRef = file.getUserData(BUFFERED_IMAGE_REF_KEY);
if (loadedTimeStamp == null || loadedTimeStamp < file.getTimeStamp() || SoftReference.dereference(imageRef) == null) {
try {
final byte[] content = file.contentsToByteArray();
InputStream inputStream = new ByteArrayInputStream(content, 0, content.length);
ImageInputStream imageInputStream = ImageIO.createImageInputStream(inputStream);
try {
Iterator<ImageReader> imageReaders = ImageIO.getImageReaders(imageInputStream);
if (imageReaders.hasNext()) {
ImageReader imageReader = imageReaders.next();
try {
file.putUserData(FORMAT_KEY, imageReader.getFormatName());
ImageReadParam param = imageReader.getDefaultReadParam();
imageReader.setInput(imageInputStream, true, true);
int minIndex = imageReader.getMinIndex();
BufferedImage image = imageReader.read(minIndex, param);
file.putUserData(BUFFERED_IMAGE_REF_KEY, new SoftReference<BufferedImage>(image));
return true;
}
finally {
imageReader.dispose();
}
}
}
finally {
imageInputStream.close();
}
}
finally {
// We perform loading no more needed
file.putUserData(TIMESTAMP_KEY, System.currentTimeMillis());
}
}
return false;
}
public static JComponent getPreviewComponent(@Nullable final PsiElement parent) {
if (parent == null) {
return null;
}
final PsiReference[] references = parent.getReferences();
for (final PsiReference reference : references) {
final PsiElement fileItem = reference.resolve();
if (fileItem instanceof PsiFileSystemItem) {
final PsiFileSystemItem item = (PsiFileSystemItem)fileItem;
if (!item.isDirectory()) {
final VirtualFile file = item.getVirtualFile();
if (file != null && supportedExtensions.contains(file.getExtension())) {
try {
refresh(file);
SoftReference<BufferedImage> imageRef = file.getUserData(BUFFERED_IMAGE_REF_KEY);
final BufferedImage image = SoftReference.dereference(imageRef);
if (image != null) {
return new ImagePreviewComponent(image, file.getLength());
}
}
catch (IOException ignored) {
// nothing
}
}
}
}
}
return null;
}
/**
* This method doesn't use caching, so if you want to use it then you should consider implementing external cache.
*/
public static ImagePreviewComponent getPreviewComponent(@NotNull final BufferedImage image, final long imageFileSize) {
return new ImagePreviewComponent(image, imageFileSize);
}
private class ImageComp extends JComponent {
private final Dimension myPreferredSize;
private ImageComp() {
if (myImage.getWidth() > 300 || myImage.getHeight() > 300) {
// will make image smaller
final float factor = 300.0f / Math.max(myImage.getWidth(), myImage.getHeight());
myPreferredSize = new Dimension((int)(myImage.getWidth() * factor), (int)(myImage.getHeight() * factor));
}
else {
myPreferredSize = new Dimension(myImage.getWidth(), myImage.getHeight());
}
}
@Override
public void paint(final Graphics g) {
super.paint(g);
Rectangle r = getBounds();
final int width = myImage.getWidth();
final int height = myImage.getHeight();
g.drawImage(myImage, 0, 0, r.width > width ? width : r.width, r.height > height ? height : r.height, this);
}
@Override
public Dimension getPreferredSize() {
return myPreferredSize;
}
@Override
public Dimension getMinimumSize() {
return getPreferredSize();
}
@Override
public Dimension getMaximumSize() {
return getPreferredSize();
}
}
}