/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ /* $Id$ */ package org.apache.fop.render.pdf; import java.awt.color.ColorSpace; import java.awt.color.ICC_Profile; import java.awt.image.IndexColorModel; import org.apache.commons.io.output.ByteArrayOutputStream; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.xmlgraphics.image.loader.Image; import org.apache.xmlgraphics.java2d.color.profile.ColorProfileUtil; import org.apache.fop.pdf.PDFArray; import org.apache.fop.pdf.PDFColor; import org.apache.fop.pdf.PDFConformanceException; import org.apache.fop.pdf.PDFDeviceColorSpace; import org.apache.fop.pdf.PDFDictionary; import org.apache.fop.pdf.PDFDocument; import org.apache.fop.pdf.PDFICCBasedColorSpace; import org.apache.fop.pdf.PDFICCStream; import org.apache.fop.pdf.PDFImage; import org.apache.fop.pdf.PDFName; import org.apache.fop.pdf.PDFReference; /** * Abstract PDFImage implementation for the PDF renderer. */ public abstract class AbstractImageAdapter implements PDFImage { /** logging instance */ private static Log log = LogFactory.getLog(AbstractImageAdapter.class); private String key; /** the image */ protected Image image; private PDFICCStream pdfICCStream; private static final int MAX_HIVAL = 255; /** * Creates a new PDFImage from an Image instance. * @param image the image * @param key XObject key */ public AbstractImageAdapter(Image image, String key) { this.image = image; this.key = key; if (log.isDebugEnabled()) { log.debug("New ImageAdapter created for key: " + key); } } /** {@inheritDoc} */ public String getKey() { // key to look up XObject return this.key; } /** * Returns the image's color space. * @return the color space */ protected ColorSpace getImageColorSpace() { return image.getColorSpace(); } /** {@inheritDoc} */ public void setup(PDFDocument doc) { ICC_Profile prof = getEffectiveICCProfile(); PDFDeviceColorSpace pdfCS = toPDFColorSpace(getImageColorSpace()); if (prof != null) { pdfICCStream = setupColorProfile(doc, prof, pdfCS); } if (doc.getProfile().getPDFAMode().isPDFA1LevelB()) { if (pdfCS != null && pdfCS.getColorSpace() != PDFDeviceColorSpace.DEVICE_RGB && pdfCS.getColorSpace() != PDFDeviceColorSpace.DEVICE_GRAY && prof == null) { //See PDF/A-1, ISO 19005:1:2005(E), 6.2.3.3 //FOP is currently restricted to DeviceRGB if PDF/A-1 is active. throw new PDFConformanceException( "PDF/A-1 does not allow mixing DeviceRGB and DeviceCMYK: " + image.getInfo()); } } } /** * Returns the effective ICC profile for the image. * @return an ICC profile or null */ protected ICC_Profile getEffectiveICCProfile() { return image.getICCProfile(); } private static PDFICCStream setupColorProfile(PDFDocument doc, ICC_Profile prof, PDFDeviceColorSpace pdfCS) { boolean defaultsRGB = ColorProfileUtil.isDefaultsRGB(prof); String desc = ColorProfileUtil.getICCProfileDescription(prof); if (log.isDebugEnabled()) { log.debug("Image returns ICC profile: " + desc + ", default sRGB=" + defaultsRGB); } PDFICCBasedColorSpace cs = doc.getResources().getICCColorSpaceByProfileName(desc); PDFICCStream pdfICCStream; if (!defaultsRGB) { if (cs == null) { pdfICCStream = doc.getFactory().makePDFICCStream(); pdfICCStream.setColorSpace(prof, pdfCS); cs = doc.getFactory().makeICCBasedColorSpace(null, null, pdfICCStream); } else { pdfICCStream = cs.getICCStream(); } } else { if (cs == null) { if (desc == null || !desc.startsWith("sRGB")) { log.warn("The default sRGB profile was indicated," + " but the profile description does not match what was expected: " + desc); } //It's the default sRGB profile which we mapped to DefaultRGB in PDFRenderer cs = (PDFICCBasedColorSpace)doc.getResources().getColorSpace( new PDFName("DefaultRGB")); } if (cs == null) { // sRGB hasn't been set up for the PDF document // so install but don't set to DefaultRGB cs = PDFICCBasedColorSpace.setupsRGBColorSpace(doc); } pdfICCStream = cs.getICCStream(); } return pdfICCStream; } /** {@inheritDoc} */ public int getWidth() { return image.getSize().getWidthPx(); } /** {@inheritDoc} */ public int getHeight() { return image.getSize().getHeightPx(); } /** {@inheritDoc} */ public boolean isTransparent() { return false; } /** {@inheritDoc} */ public PDFColor getTransparentColor() { return null; } /** {@inheritDoc} */ public String getMask() { return null; } /** @return null (if not overridden) */ public String getSoftMask() { return null; } /** {@inheritDoc} */ public PDFReference getSoftMaskReference() { return null; } /** {@inheritDoc} */ public boolean isInverted() { return false; } /** {@inheritDoc} */ public boolean isPS() { return false; } /** {@inheritDoc} */ public PDFICCStream getICCStream() { return pdfICCStream; } /** {@inheritDoc} */ public void populateXObjectDictionary(PDFDictionary dict) { //nop } /** * This is to be used by populateXObjectDictionary() when the image is palette based. * @param dict the dictionary to fill in * @param icm the image color model */ protected void populateXObjectDictionaryForIndexColorModel(PDFDictionary dict, IndexColorModel icm) { PDFArray indexed = new PDFArray(dict); indexed.add(new PDFName("Indexed")); if (icm.getColorSpace().getType() != ColorSpace.TYPE_RGB) { log.warn("Indexed color space is not using RGB as base color space." + " The image may not be handled correctly." + " Base color space: " + icm.getColorSpace() + " Image: " + image.getInfo()); } indexed.add(new PDFName(toPDFColorSpace(icm.getColorSpace()).getName())); int c = icm.getMapSize(); int hival = c - 1; if (hival > MAX_HIVAL) { throw new UnsupportedOperationException("hival must not go beyond " + MAX_HIVAL); } indexed.add(Integer.valueOf(hival)); int[] palette = new int[c]; icm.getRGBs(palette); ByteArrayOutputStream baout = new ByteArrayOutputStream(); for (int i = 0; i < c; i++) { // TODO Probably doesn't work for non RGB based color spaces // See log warning above int entry = palette[i]; baout.write((entry & 0xFF0000) >> 16); baout.write((entry & 0xFF00) >> 8); baout.write(entry & 0xFF); } indexed.add(baout.toByteArray()); dict.put("ColorSpace", indexed); dict.put("BitsPerComponent", icm.getPixelSize()); Integer index = getIndexOfFirstTransparentColorInPalette(icm); if (index != null) { PDFArray mask = new PDFArray(dict); mask.add(index); mask.add(index); dict.put("Mask", mask); } } private static Integer getIndexOfFirstTransparentColorInPalette(IndexColorModel icm) { byte[] alphas = new byte[icm.getMapSize()]; byte[] reds = new byte[icm.getMapSize()]; byte[] greens = new byte[icm.getMapSize()]; byte[] blues = new byte[icm.getMapSize()]; icm.getAlphas(alphas); icm.getReds(reds); icm.getGreens(greens); icm.getBlues(blues); for (int i = 0; i < icm.getMapSize(); i++) { if ((alphas[i] & 0xFF) == 0) { return Integer.valueOf(i); } } return null; } /** * Converts a ColorSpace object to a PDFColorSpace object. * @param cs ColorSpace instance * @return PDFColorSpace new converted object */ public static PDFDeviceColorSpace toPDFColorSpace(ColorSpace cs) { if (cs == null) { return null; } PDFDeviceColorSpace pdfCS = new PDFDeviceColorSpace(0); switch (cs.getType()) { case ColorSpace.TYPE_CMYK: pdfCS.setColorSpace(PDFDeviceColorSpace.DEVICE_CMYK); break; case ColorSpace.TYPE_GRAY: pdfCS.setColorSpace(PDFDeviceColorSpace.DEVICE_GRAY); break; default: pdfCS.setColorSpace(PDFDeviceColorSpace.DEVICE_RGB); } return pdfCS; } }