com.pronoiahealth.olhie.server.services.BookCoverImageService.java Source code

Java tutorial

Introduction

Here is the source code for com.pronoiahealth.olhie.server.services.BookCoverImageService.java

Source

/*******************************************************************************
 * Copyright (c) 2013 Pronoia Health LLC.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Pronoia Health LLC - initial API and implementation
 *******************************************************************************/
package com.pronoiahealth.olhie.server.services;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.image.BufferedImage;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.servlet.ServletContext;

import org.apache.commons.imaging.ImageFormat;
import org.apache.commons.imaging.Imaging;
import org.apache.commons.imaging.palette.PaletteFactory;
import org.apache.commons.io.IOUtils;
import org.apache.deltaspike.servlet.api.Web;

import com.lowagie.text.pdf.codec.Base64;
import com.pronoiahealth.olhie.client.shared.vo.Book;
import com.pronoiahealth.olhie.client.shared.vo.BookCategory;
import com.pronoiahealth.olhie.client.shared.vo.BookCover;
import com.pronoiahealth.olhie.server.utils.ImageUtils;

/**
 * BookCoverImageService.java<br/>
 * Responsibilities:<br/>
 * 1.
 * 
 * @author John DeStefano
 * @version 1.0
 * @since Sep 11, 2013
 * 
 */
@ApplicationScoped
public class BookCoverImageService {
    @Inject
    private Logger log;

    @Inject
    @Web
    private ServletContext servletContext;

    @Inject
    private TempThemeHolder coverDisplayData;

    public enum Cover {
        FRONT, BACK, SMALL_FRONT
    };

    private Font medulaOneRegularFont;
    private Font medulaOneRegularFont48;
    private Font arialBoldFont13;
    private Font arialBoldFont16;
    private Map<String, byte[]> coverMap;
    private Hashtable<TextAttribute, Object> authorFontMap;
    private Hashtable<TextAttribute, Object> titleFontMap;
    private Hashtable<TextAttribute, Object> backTitleFontMap;
    private PaletteFactory palFac;

    /**
     * Constructor
     * 
     */
    public BookCoverImageService() {
    }

    /**
     * Initialize the various maps, fonts, etc..
     */
    @PostConstruct
    protected void postActivate() {
        try {
            // Init the coverMap and load the images
            coverMap = new HashMap<String, byte[]>();
            List<BookCover> covers = coverDisplayData.getCovers();
            for (BookCover cover : covers) {
                coverMap.put(cover.getCoverName(),
                        readResourceToByteArray("/" + cover.getImgUrl(), servletContext));
            }

            // Load needed fonts
            medulaOneRegularFont = Font.createFont(Font.TRUETYPE_FONT,
                    servletContext.getResourceAsStream("/Olhie/font/MedulaOne-Regular.ttf"));
            medulaOneRegularFont48 = medulaOneRegularFont.deriveFont(new Float(48.0));
            arialBoldFont13 = new Font("Arial Bold", Font.BOLD, 13);
            arialBoldFont16 = new Font("Arial Bold", Font.ITALIC, 16);

            // Init font maps
            // author
            authorFontMap = new Hashtable<TextAttribute, Object>();
            authorFontMap.put(TextAttribute.FONT, arialBoldFont16);
            authorFontMap.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);

            // Title
            titleFontMap = new Hashtable<TextAttribute, Object>();
            titleFontMap.put(TextAttribute.FONT, medulaOneRegularFont48);
            titleFontMap.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);

            // Back cover title
            backTitleFontMap = new Hashtable<TextAttribute, Object>();
            backTitleFontMap.put(TextAttribute.FONT, arialBoldFont13);
            backTitleFontMap.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
            backTitleFontMap.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_LOW_ONE_PIXEL);

            // Build palette factory
            palFac = new PaletteFactory();

        } catch (Exception e) {
            log.log(Level.SEVERE, "Error occured during BookCoverImageService initialization.", e);
        }
    }

    /**
     * Method to return both the front and back covers in a map
     * 
     * 
     * @param coverId
     * @param logoBytes
     * @param authorStr
     * @param titleStr
     * @param width
     * @param height
     * @param type
     * @return
     * @throws Exception
     */
    public Map<Cover, String> createBookCovers(String coverId, byte[] logoBytes, String authorStr, String titleStr,
            String spineColor, String authorTextColor, String titleTextColor, int width, int height, int type,
            ImageFormat imgFormat, int maxColors) throws Exception {
        // Create the return make
        Map<Cover, String> retMap = new HashMap<Cover, String>();

        // Front cover
        retMap.put(Cover.FRONT, createFrontCoverEncoded(coverId, logoBytes, authorStr, titleStr, spineColor,
                authorTextColor, titleTextColor, width, height, type, imgFormat, maxColors));

        // Back cover
        retMap.put(Cover.BACK, createBackCoverEncoded(coverId, titleStr, spineColor, titleTextColor, width, height,
                type, imgFormat, maxColors));

        // Return the map with the front and back cover
        return retMap;
    }

    /**
     * @param book
     * @param category
     * @param cover
     * @param logo
     * @param user
     * @return
     * @throws Exception
     */
    public Map<Cover, String> createDefaultBookCovers(Book book, BookCategory category, BookCover cover,
            byte[] logo, String authorName) throws Exception {
        // Create the return make
        Map<Cover, String> retMap = new HashMap<Cover, String>();

        // Front cover
        retMap.put(Cover.FRONT, createDefaultFrontCoverEncoded(book, category, cover, logo, authorName));

        // Back cover
        retMap.put(Cover.BACK, createDefaultBackCoverEncoded(book, cover, category));

        // Small front cover
        retMap.put(Cover.SMALL_FRONT, createDefaultSmallFrontCoverEncoded(book, category, cover, logo, authorName));

        // Return the map with the front and back cover
        return retMap;
    }

    /**
     * @param book
     * @param category
     * @param cover
     * @param logo
     * @param authorName
     * @return
     * @throws Exception
     */
    public Map<Cover, byte[]> createDefaultBookCoversBytes(Book book, BookCategory category, BookCover cover,
            byte[] logo, String authorName) throws Exception {

        // Create the return make
        Map<Cover, byte[]> retMap = new HashMap<Cover, byte[]>();

        // Front cover
        retMap.put(Cover.FRONT, createDefaultFrontCover(book, category, cover, logo, authorName));

        // Back cover
        retMap.put(Cover.BACK, createDefaultBackCover(book, cover, category));

        // Small front cover
        retMap.put(Cover.SMALL_FRONT, createDefaultSmallFrontCover(book, category, cover, logo, authorName));

        // Return the map with the front and back cover
        return retMap;

    }

    /**
     * Create the front cover
     * 
     * @param coverId
     * @param logoBytes
     * @param authorStr
     * @param titleStr
     * @param textColor
     *            - ex. #FFFFFF
     * @param width
     * @param height
     * @param type
     *            - ex. BufferedImage.TYPE_INT_ARGB
     * @param imgFormat
     *            - ex. ImageFormat.IMAGE_FORMAT_PNG
     * @return
     * @throws Exception
     */
    public byte[] createFrontCover(String coverId, byte[] logoBytes, String authorStr, String titleStr,
            String spineColor, String authorTextColor, String titleTextColor, int width, int height, int type,
            ImageFormat imgFormat, int maxColors) throws Exception {

        Graphics2D g = null;

        try {
            // Front cover first
            // Read in base cover image
            BufferedImage coverImg = Imaging.getBufferedImage(coverMap.get(coverId));

            // Resize cover image to the basic 300 X 400 for front cover
            BufferedImage frontCoverImg = resize(coverImg, 300, 400, type);
            g = (Graphics2D) frontCoverImg.getGraphics();

            // Draw logo if present
            if (logoBytes != null && logoBytes.length > 0) {
                // Resize logo to 200x100
                BufferedImage logoImg = Imaging.getBufferedImage(logoBytes);
                BufferedImage outLogo = null;
                int logoHeight = logoImg.getHeight();
                int logoWidth = logoImg.getWidth();

                if (logoHeight > 100 || logoWidth > 200) {
                    outLogo = this.resize(logoImg, logoWidth > 200 ? 200 : logoWidth,
                            logoHeight > 100 ? 100 : logoHeight, type);
                } else {
                    outLogo = logoImg;
                }

                // Add to coverImg
                g.drawImage(outLogo, 32, 25, null);
            }

            // Add spine if present
            if (spineColor != null && spineColor.length() > 0) {
                g.setColor(Color.decode(spineColor));
                g.fillRect(0, 0, 2, frontCoverImg.getHeight());
            }

            // Add author if present
            if (authorStr != null && authorStr.length() > 0) {
                // Add author text to image
                BufferedImage authorTextImg = createText(40, 220, authorStr, authorTextColor, false, authorFontMap,
                        type);
                g.drawImage(authorTextImg, 30, 215, null);
            }

            // Add title if present
            if (titleStr != null && titleStr.length() > 0) {
                BufferedImage titleTextImg = createText(100, 220, titleStr, titleTextColor, false, titleFontMap,
                        type);
                g.drawImage(titleTextImg, 30, 240, null);
            }

            // If the requested size is not 300X400 convert the image
            BufferedImage outImg = null;
            if (width != 300 || height != 400) {
                outImg = resize(frontCoverImg, width, height, type);
            } else {
                outImg = frontCoverImg;
            }

            // Do we want a PNG with a fixed number of colors
            if (maxColors >= 2 && imgFormat == ImageFormat.IMAGE_FORMAT_PNG) {
                outImg = ImageUtils.reduce32(outImg, maxColors);
            }

            // Return bytes
            Map<String, Object> params = new HashMap<String, Object>();
            byte[] outBytes = Imaging.writeImageToBytes(outImg, imgFormat, params);
            return outBytes;
        } finally {
            if (g != null) {
                g.dispose();
            }
        }
    }

    /**
     * Create the front cover and return as base 64 encoded string
     * 
     * @param coverId
     * @param logoBytes
     * @param authorStr
     * @param titleStr
     * @param textColor
     *            - ex. #FFFFFF
     * @param width
     * @param height
     * @param type
     *            - ex. BufferedImage.TYPE_INT_ARGB
     * @param imgFormat
     *            - ex. ImageFormat.IMAGE_FORMAT_PNG
     * @return
     * @throws Exception
     */
    public String createFrontCoverEncoded(String coverId, byte[] logoBytes, String authorStr, String titleStr,
            String spineColor, String authorTextColor, String titleTextColor, int width, int height, int type,
            ImageFormat imgFormat, int maxColors) throws Exception {

        return Base64.encodeBytes(createFrontCover(coverId, logoBytes, authorStr, titleStr, spineColor,
                authorTextColor, titleTextColor, width, height, type, imgFormat, maxColors));
    }

    /**
     * Creates a small front cover
     * 
     * @param book
     * @param category
     * @param cover
     * @param logo
     * @param authorName
     * @return
     * @throws Exception
     */
    public String createDefaultSmallFrontCoverEncoded(Book book, BookCategory category, BookCover cover,
            byte[] logo, String authorName) throws Exception {
        return createFrontCoverEncoded(book.getCoverName(), logo, authorName, book.getBookTitle(),
                category.getColor(), cover.getAuthorTextColor(), cover.getCoverTitleTextColor(), 75, 100,
                BufferedImage.TYPE_INT_ARGB, ImageFormat.IMAGE_FORMAT_PNG, 128);
    }

    /**
     * Creates a small front cover
     * 
     * @param book
     * @param category
     * @param cover
     * @param logo
     * @param authorName
     * @return
     * @throws Exception
     */
    public byte[] createDefaultSmallFrontCover(Book book, BookCategory category, BookCover cover, byte[] logo,
            String authorName) throws Exception {
        return createFrontCover(book.getCoverName(), logo, authorName, book.getBookTitle(), category.getColor(),
                cover.getAuthorTextColor(), cover.getCoverTitleTextColor(), 75, 100, BufferedImage.TYPE_INT_ARGB,
                ImageFormat.IMAGE_FORMAT_PNG, 128);
    }

    /**
     * 
     * @param book
     * @param category
     * @param cover
     * @param logo
     * @param author
     * @return
     * @throws Exception
     */
    public String createDefaultFrontCoverEncoded(Book book, BookCategory category, BookCover cover, byte[] logo,
            String authorName) throws Exception {
        return createFrontCoverEncoded(book.getCoverName(), logo, authorName, book.getBookTitle(),
                category.getColor(), cover.getAuthorTextColor(), cover.getCoverTitleTextColor(), 300, 400,
                BufferedImage.TYPE_INT_ARGB, ImageFormat.IMAGE_FORMAT_PNG, 128);
    }

    public byte[] createDefaultFrontCover(Book book, BookCategory category, BookCover cover, byte[] logo,
            String authorName) throws Exception {
        return createFrontCover(book.getCoverName(), logo, authorName, book.getBookTitle(), category.getColor(),
                cover.getAuthorTextColor(), cover.getCoverTitleTextColor(), 300, 400, BufferedImage.TYPE_INT_ARGB,
                ImageFormat.IMAGE_FORMAT_PNG, 128);
    }

    /**
     * Create a back cover
     * 
     * @param coverId
     * @param titleStr
     * @param textColor
     * @param width
     * @param height
     * @param type
     * @param imgFormat
     * @param macColors
     *            - for PNG images reduce the color palette (must be greater
     *            than 2)
     * @return
     * @throws Exception
     */
    public byte[] createBackCover(String coverId, String titleStr, String spineColor, String textColor, int width,
            int height, int type, ImageFormat imgFormat, int maxColors) throws Exception {

        Graphics2D g2D = null;

        try {
            // Front cover first
            // Read in base cover image
            BufferedImage coverImg = Imaging.getBufferedImage(coverMap.get(coverId));

            // Resize cover image to the basic 300 X 400 for front cover
            BufferedImage backCoverImg = resize(coverImg, 300, 400, type);
            g2D = (Graphics2D) backCoverImg.getGraphics();

            // Add title if present
            if (titleStr != null && titleStr.length() > 0) {
                BufferedImage titleTextImg = createText(82, 220, titleStr, textColor, true, backTitleFontMap, type);
                g2D.drawImage(titleTextImg, 40, 35, null);
            }

            // Add spine if present
            if (spineColor != null && spineColor.length() > 0) {
                g2D.setColor(Color.decode(spineColor));
                g2D.fillRect(backCoverImg.getWidth() - 2, 0, 2, backCoverImg.getHeight());
            }

            // If the requested size is not 300X400 convert the image
            BufferedImage outImg = null;
            if (width != 300 || height != 400) {
                outImg = resize(backCoverImg, width, height, type);
            } else {
                outImg = backCoverImg;
            }

            // Do we want a PNG with a fixed number of colors
            if (maxColors >= 2 && imgFormat == ImageFormat.IMAGE_FORMAT_PNG) {
                outImg = ImageUtils.reduce32(outImg, maxColors);
            }

            // Return bytes
            Map<String, Object> params = new HashMap<String, Object>();
            byte[] outBytes = Imaging.writeImageToBytes(outImg, imgFormat, params);
            return outBytes;
        } finally {
            if (g2D != null) {
                g2D.dispose();
            }
        }
    }

    /**
     * Create a back cover
     * 
     * @param coverId
     * @param titleStr
     * @param textColor
     * @param width
     * @param height
     * @param type
     * @param imgFormat
     * @return
     * @throws Exception
     */
    public String createBackCoverEncoded(String coverId, String titleStr, String spineColor, String textColor,
            int width, int height, int type, ImageFormat imgFormat, int maxColors) throws Exception {

        return Base64.encodeBytes(createBackCover(coverId, titleStr, spineColor, textColor, width, height, type,
                imgFormat, maxColors));
    }

    /**
     * @param book
     * @param category
     * @return
     * @throws Exception
     */
    public String createDefaultBackCoverEncoded(Book book, BookCover cover, BookCategory category)
            throws Exception {
        return createBackCoverEncoded(book.getCoverName(), book.getBookTitle(), category.getColor(),
                cover.getCoverTitleTextColor(), 300, 400, BufferedImage.TYPE_INT_ARGB, ImageFormat.IMAGE_FORMAT_PNG,
                128);
    }

    /**
     * @param book
     * @param category
     * @return
     * @throws Exception
     */
    public byte[] createDefaultBackCover(Book book, BookCover cover, BookCategory category) throws Exception {
        return createBackCover(book.getCoverName(), book.getBookTitle(), category.getColor(),
                cover.getCoverTitleTextColor(), 300, 400, BufferedImage.TYPE_INT_ARGB, ImageFormat.IMAGE_FORMAT_PNG,
                128);
    }

    /**
     * Create an image that contains text
     * 
     * @param height
     * @param width
     * @param text
     * @param textColor
     * @param center
     * @param fontMap
     * @param type
     * @return
     */
    private BufferedImage createText(int height, int width, String text, String textColor, boolean center,
            Map<TextAttribute, Object> fontMap, int type) {
        BufferedImage img = new BufferedImage(width, height, type);
        Graphics2D g2d = null;
        try {
            g2d = (Graphics2D) img.getGraphics();

            // Create attributed text
            AttributedString txt = new AttributedString(text, fontMap);

            // Set graphics color
            g2d.setColor(Color.decode(textColor));

            // Create a new LineBreakMeasurer from the paragraph.
            // It will be cached and re-used.
            AttributedCharacterIterator paragraph = txt.getIterator();
            int paragraphStart = paragraph.getBeginIndex();
            int paragraphEnd = paragraph.getEndIndex();
            FontRenderContext frc = g2d.getFontRenderContext();
            LineBreakMeasurer lineMeasurer = new LineBreakMeasurer(paragraph, frc);

            // Set break width to width of Component.
            float breakWidth = (float) width;
            float drawPosY = 0;
            // Set position to the index of the first character in the
            // paragraph.
            lineMeasurer.setPosition(paragraphStart);

            // Get lines until the entire paragraph has been displayed.
            while (lineMeasurer.getPosition() < paragraphEnd) {
                // Retrieve next layout. A cleverer program would also cache
                // these layouts until the component is re-sized.
                TextLayout layout = lineMeasurer.nextLayout(breakWidth);

                // Compute pen x position. If the paragraph is right-to-left we
                // will align the TextLayouts to the right edge of the panel.
                // Note: drawPosX is always where the LEFT of the text is
                // placed.
                float drawPosX = layout.isLeftToRight() ? 0 : breakWidth - layout.getAdvance();

                if (center == true) {
                    double xOffSet = (width - layout.getBounds().getWidth()) / 2;
                    drawPosX = drawPosX + new Float(xOffSet);
                }

                // Move y-coordinate by the ascent of the layout.
                drawPosY += layout.getAscent();

                // Draw the TextLayout at (drawPosX, drawPosY).
                layout.draw(g2d, drawPosX, drawPosY);

                // Move y-coordinate in preparation for next layout.
                drawPosY += layout.getDescent() + layout.getLeading();
            }
        } finally {
            if (g2d != null) {
                g2d.dispose();
            }
        }
        return img;
    }

    /**
     * Resize an image
     * 
     * @param originalImage
     * @param width
     * @param height
     * @param type
     * @return
     */
    private BufferedImage resize(BufferedImage originalImage, int width, int height, int type) {
        BufferedImage resizedImage = new BufferedImage(width, height, type);
        Graphics2D g = resizedImage.createGraphics();
        try {
            g.setComposite(AlphaComposite.Src);
            g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g.drawImage(originalImage, 0, 0, width, height, null);
        } finally {
            if (g != null) {
                g.dispose();
            }
        }

        return resizedImage;
    }

    /**
     * Assists in preloading images
     * 
     * @param resourceLocation
     * @param loaderClass
     * @return
     * @throws Exception
     */
    private byte[] readResourceToByteArray(String resourceLocation, ServletContext servletContext)
            throws Exception {
        return IOUtils.toByteArray(servletContext.getResourceAsStream(resourceLocation));
    }
}