net.pms.util.GenericIcons.java Source code

Java tutorial

Introduction

Here is the source code for net.pms.util.GenericIcons.java

Source

/*
 * Universal Media Server, for streaming any media to DLNA
 * compatible renderers based on the http://www.ps3mediaserver.org.
 * Copyright (C) 2012 UMS developers.
 *
 * This program is a free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; version 2
 * of the License only.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package net.pms.util;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
import javax.imageio.ImageIO;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.pms.PMS;
import net.pms.dlna.DLNAMediaInfo;
import net.pms.dlna.DLNAResource;
import net.pms.dlna.DLNAThumbnail;
import net.pms.dlna.DLNAThumbnailInputStream;
import net.pms.formats.Format;
import net.pms.image.ImageFormat;
import net.pms.image.ImageIOTools;
import net.pms.image.ImagesUtil.ScaleType;

/**
 * This is an singleton class for providing and caching generic file extension
 * icons. Thread-safe.
 */

public enum GenericIcons {
    INSTANCE;

    private final BufferedImage genericAudioIcon = readBufferedImage("formats/audio.png");
    private final BufferedImage genericImageIcon = readBufferedImage("formats/image.png");
    private final BufferedImage genericVideoIcon = readBufferedImage("formats/video.png");
    private final BufferedImage genericUnknownIcon = readBufferedImage("formats/unknown.png");
    private final DLNAThumbnail genericFolderThumbnail;
    private final ReentrantLock cacheLock = new ReentrantLock();
    /**
     * All access to {@link #cache} must be protected with {@link #cacheLock}.
     */
    private final Map<ImageFormat, Map<IconType, Map<String, DLNAThumbnail>>> cache = new HashMap<>();
    private static final Logger LOGGER = LoggerFactory.getLogger(GenericIcons.class);

    private GenericIcons() {
        DLNAThumbnail thumbnail;
        try {
            thumbnail = DLNAThumbnail.toThumbnail(getResourceAsStream("thumbnail-folder-256.png"));
        } catch (IOException e) {
            thumbnail = null;
        }
        genericFolderThumbnail = thumbnail;
    }

    public DLNAThumbnailInputStream getGenericIcon(DLNAResource resource) {
        ImageFormat imageFormat = ImageFormat.JPEG;

        if (resource == null) {
            ImageIO.setUseCache(false);
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            try {
                ImageIOTools.imageIOWrite(genericUnknownIcon, imageFormat.toString(), out);
                return DLNAThumbnailInputStream.toThumbnailInputStream(out.toByteArray());
            } catch (IOException e) {
                LOGGER.warn("Unexpected error while generating generic thumbnail for null resource: {}",
                        e.getMessage());
                LOGGER.trace("", e);
                return null;
            }
        }

        IconType iconType = IconType.UNKNOWN;
        if (resource.getMedia() != null) {
            if (resource.getMedia().isAudio()) {
                iconType = IconType.AUDIO;
            } else if (resource.getMedia().isImage()) {
                iconType = IconType.IMAGE;
            } else if (resource.getMedia().isVideo()) {
                // FFmpeg parses images as video, try to rectify
                if (resource.getFormat() != null && resource.getFormat().isImage()) {
                    iconType = IconType.IMAGE;
                } else {
                    iconType = IconType.VIDEO;
                }
            }
        } else if (resource.getFormat() != null) {
            if (resource.getFormat().isAudio()) {
                iconType = IconType.AUDIO;
            } else if (resource.getFormat().isImage()) {
                iconType = IconType.IMAGE;
            } else if (resource.getFormat().isVideo()) {
                iconType = IconType.VIDEO;
            }
        }

        DLNAThumbnail image = null;
        cacheLock.lock();
        try {
            if (!cache.containsKey(imageFormat)) {
                cache.put(imageFormat, new HashMap<IconType, Map<String, DLNAThumbnail>>());
            }
            Map<IconType, Map<String, DLNAThumbnail>> typeCache = cache.get(imageFormat);

            if (!typeCache.containsKey(iconType)) {
                typeCache.put(iconType, new HashMap<String, DLNAThumbnail>());
            }
            Map<String, DLNAThumbnail> imageCache = typeCache.get(iconType);

            String label = getLabelFromImageFormat(resource.getMedia());
            if (label == null) {
                label = getLabelFromFormat(resource.getFormat());
            }
            if (label == null) {
                label = getLabelFromContainer(resource.getMedia());
            }
            if (label != null && label.length() < 5) {
                label = label.toUpperCase(Locale.ROOT);
            } else if (label != null && label.toLowerCase(Locale.ROOT).equals(label)) {
                label = StringUtils.capitalize(label);
            }

            if (imageCache.containsKey(label)) {
                return DLNAThumbnailInputStream.toThumbnailInputStream(imageCache.get(label));
            }

            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace("Creating generic {} thumbnail for {} ({})", iconType.toString().toLowerCase(),
                        label.toUpperCase(), imageFormat);
            }

            try {
                image = addFormatLabelToImage(label, imageFormat, iconType);
            } catch (IOException e) {
                LOGGER.warn("Unexpected error while generating generic thumbnail for \"{}\": {}",
                        resource.getName(), e.getMessage());
                LOGGER.trace("", e);
            }

            imageCache.put(label, image);
        } finally {
            cacheLock.unlock();
        }

        return DLNAThumbnailInputStream.toThumbnailInputStream(image);
    }

    public DLNAThumbnailInputStream getGenericFolderIcon() {
        return DLNAThumbnailInputStream.toThumbnailInputStream(genericFolderThumbnail);
    }

    private String getLabelFromImageFormat(DLNAMediaInfo mediaInfo) {
        return mediaInfo != null && mediaInfo.isImage() && mediaInfo.getImageInfo() != null
                && mediaInfo.getImageInfo().getFormat() != null ? mediaInfo.getImageInfo().getFormat().toString()
                        : null;
    }

    private String getLabelFromFormat(Format format) {
        if (format == null || format.getIdentifier() == null) {
            return null;
        }
        // Replace some Identifier names with prettier ones
        switch (format.getIdentifier()) {
        case AUDIO_AS_VIDEO:
            return "Audio as Video";
        case MICRODVD:
            return "MicroDVD";
        case SUBRIP:
            return "SubRip";
        case THREEG2A:
            return "3G2A";
        case THREEGA:
            return "3GA";
        case WEBVTT:
            return "WebVTT";
        default:
            return format.getIdentifier().toString();
        }
    }

    private String getLabelFromContainer(DLNAMediaInfo mediaInfo) {
        return mediaInfo != null ? mediaInfo.getContainer() : null;
    }

    /**
     * Add the format(container) name of the media to the generic icon image.
     *
     * @param image BufferdImage to be the label added
     * @param label the media container name to be added as a label
     * @param renderer the renderer configuration
     *
     * @return the generic icon with the container label added and scaled in accordance with renderer setting
     */
    private DLNAThumbnail addFormatLabelToImage(String label, ImageFormat imageFormat, IconType iconType)
            throws IOException {

        BufferedImage image;
        switch (iconType) {
        case AUDIO:
            image = genericAudioIcon;
            break;
        case IMAGE:
            image = genericImageIcon;
            break;
        case VIDEO:
            image = genericVideoIcon;
            break;
        default:
            image = genericUnknownIcon;
        }

        if (image != null) {
            // Make a copy
            ColorModel colorModel = image.getColorModel();
            image = new BufferedImage(colorModel, image.copyData(null), colorModel.isAlphaPremultiplied(), null);
        }

        ByteArrayOutputStream out = null;

        if (label != null && image != null) {
            out = new ByteArrayOutputStream();
            Graphics2D g = image.createGraphics();
            g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
            g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

            try {
                int size = 40;
                Font font = new Font(Font.SANS_SERIF, Font.BOLD, size);
                FontMetrics metrics = g.getFontMetrics(font);
                while (size > 7 && metrics.stringWidth(label) > 135) {
                    size--;
                    font = new Font(Font.SANS_SERIF, Font.BOLD, size);
                    metrics = g.getFontMetrics(font);
                }
                // Text center point 127x, 49y - calculate centering coordinates
                int x = 127 - metrics.stringWidth(label) / 2;
                int y = 46 + metrics.getAscent() / 2;
                g.drawImage(image, 0, 0, null);
                g.setColor(Color.WHITE);
                g.setFont(font);
                g.drawString(label, x, y);

                ImageIO.setUseCache(false);
                ImageIOTools.imageIOWrite(image, imageFormat.toString(), out);
            } finally {
                g.dispose();
            }
        }
        return out != null ? DLNAThumbnail.toThumbnail(out.toByteArray(), 0, 0, ScaleType.MAX, imageFormat, false)
                : null;
    }

    /**
     * Reads a resource from a given resource path into a
     * {@link BufferedImage}. {@code /resources/images/} is already
     * prepended to the path and only the rest should be specified.
     *
     * @param resourcePath the path to the resource relative to
     * {@code /resources/images/}}
        
     * @return The {@link BufferedImage} created from the specified resource or
     *         {@code null} if the path is invalid.
     */
    protected BufferedImage readBufferedImage(String resourcePath) {
        InputStream inputStream = getResourceAsStream(resourcePath);
        if (inputStream != null) {
            try {
                return ImageIO.read(inputStream);
            } catch (IOException e) {
                LOGGER.error("Could not read resource \"{}\": {}", resourcePath, e.getMessage());
                LOGGER.trace("", e);
                return null;
            }
        }
        return null;
    }

    protected InputStream getResourceAsStream(String resourcePath) {
        return PMS.class.getResourceAsStream("/resources/images/" + resourcePath);
    }

    protected static enum IconType {
        AUDIO, IMAGE, UNKNOWN, VIDEO
    }
}