Package net.sourceforge.jiu.codecs

Source Code of net.sourceforge.jiu.codecs.PCDCodec

/*
* PCDCodec
*
* Copyright (c) 2000, 2001, 2002, 2003, 2004, 2005, 2006 Marco Schmidt.
* All rights reserved.
*/

package net.sourceforge.jiu.codecs;

import java.io.IOException;
import java.io.RandomAccessFile;
import net.sourceforge.jiu.codecs.ImageCodec;
import net.sourceforge.jiu.codecs.InvalidFileStructureException;
import net.sourceforge.jiu.codecs.UnsupportedTypeException;
import net.sourceforge.jiu.codecs.WrongFileFormatException;
import net.sourceforge.jiu.color.YCbCrIndex;
import net.sourceforge.jiu.color.conversion.PCDYCbCrConversion;
import net.sourceforge.jiu.data.Gray8Image;
import net.sourceforge.jiu.data.IntegerImage;
import net.sourceforge.jiu.data.MemoryGray8Image;
import net.sourceforge.jiu.data.MemoryRGB24Image;
import net.sourceforge.jiu.data.RGB24Image;
import net.sourceforge.jiu.ops.MissingParameterException;
import net.sourceforge.jiu.ops.OperationFailedException;
import net.sourceforge.jiu.util.ArrayRotation;
import net.sourceforge.jiu.util.ArrayScaling;

/**
* A codec to read Kodak Photo-CD (image pac) image files.
* Typical file extension is <code>.pcd</code>.
* PCD is designed to store the same image in several resolutions.
* Not all resolutions are always present in a file.
* Typically, the first five resolutions are available and the file size
* is between four and six megabytes.
* Lossless compression (Huffman encoding) is used to store the higher resolution images.
* All images are in 24 bit YCbCr colorspace, with a component subsampling of 4:1:1 (Y:Cb:Cr)
* in both horizontal and vertical direction.
* <h3>Limitations</h3>
* Only the lowest three resolutions are supported by this codec.
* <h3>Sample PCD files</h3>
* You can download sample PCD image files from
* <a href="http://www.kodak.com/digitalImaging/samples/imageIntro.shtml">Kodak's
* website</a>.
*
* @author Marco Schmidt
*/
public class PCDCodec extends ImageCodec implements YCbCrIndex
{
  /**
   * Base/16, the minimum pixel resolution, 192 x 128 pixels.
   */
  public static final int PCD_RESOLUTION_1 = 0;

  /**
   * Base/4, the second pixel resolution, 384 x 256 pixels.
   */
  public static final int PCD_RESOLUTION_2 = 1;

  /**
   * Base, the third pixel resolution, 768 x 512 pixels.
   */
  public static final int PCD_RESOLUTION_3 = 2;

  /**
   * Base*4, the fourth pixel resolution, 1536 x 1024 pixels. <em>Unsupported</em>
   */
  public static final int PCD_RESOLUTION_4 = 3;

  /**
   * Base*16, the fifth pixel resolution, 3072 x 2048 pixels. <em>Unsupported</em>
   */
  public static final int PCD_RESOLUTION_5 = 4;

  /**
   * Base*64, the sixth pixel resolution, 6144 x 4096 pixels. <em>Unsupported</em>
   */
  public static final int PCD_RESOLUTION_6 = 5;

  /**
   * Index for the default resolution , Base ({@link #PCD_RESOLUTION_3}).
   */
  public static final int PCD_RESOLUTION_DEFAULT = PCD_RESOLUTION_3;

  /**
   * This two-dimensional int array holds all possible pixel resolutions for
   * a PCD file. Use one of the PCD resolution constants (e.g.
   * {@link #PCD_RESOLUTION_3} as first index.
   * The second index must be 0 or 1 and leads to either width or
   * height.
   * Example: <code>PCD_RESOLUTION[PCD_RESOLUTION_3][1]</code> will evalute
   * as 512, which can be width or height, depending on the image being
   * in landscape or portrait mode.
   * You may want to use these resolution values in your program
   * to prompt the user which resolution to load from the file.
   */
  public static final int[][] PCD_RESOLUTIONS =
    {{192, 128}, {384, 256}, {768, 512},
     {1536, 1024}, {3072, 2048}, {6144, 4096}};
  // offsets into the file for the three uncompressed resolutions
  private static final long[] PCD_FILE_OFFSETS =
    {0x2000, 0xb800, 0x30000};
  /*private static final long[] PCD_BASE_LENGTH =
    {0x2000, 0xb800, 0x30000};*/
  // some constants to understand the orientation of an image
  private static final int NO_ROTATION = 0;
  private static final int ROTATE_90_LEFT = 1;
  private static final int ROTATE_180 = 2;
  private static final int ROTATE_90_RIGHT = 3;
  // 2048 bytes
  private static final int SECTOR_SIZE = 0x800;
  // "PCD_IPI"
  private static final byte[] MAGIC =
    {0x50, 0x43, 0x44, 0x5f, 0x49, 0x50, 0x49};
  private boolean performColorConversion;
  private boolean monochrome;
  private int numChannels;
  private int resolutionIndex;
  private RandomAccessFile in;
  private byte[][] data;

  /**
   * This constructor chooses the default settings for PCD image loading:
   * <ul>
   * <li>load color image (all channels, not only luminance)</li>
   * <li>perform color conversion from PCD's native YCbCr color space to RGB</li>
   * <li>load the image in the default resolution
   *  {@link #PCD_RESOLUTION_DEFAULT}, 768 x 512 pixels (or vice versa)</li>
   * </ul>
   */
  public PCDCodec()
  {
    super();
    setColorConversion(true);
    setMonochrome(false);
    setResolutionIndex(PCD_RESOLUTION_DEFAULT);
  }

  private byte[][] allocateMemory()
  {
    int numPixels = PCD_RESOLUTIONS[resolutionIndex][0] *
      PCD_RESOLUTIONS[resolutionIndex][1];
    byte[][] result = new byte[numChannels][];
    for (int i = 0; i < numChannels; i++)
    {
      result[i] = new byte[numPixels];
    }
    return result;
  }

  /*private void checkByteArray(
    byte[][] data,
    int numPixels) throws IllegalArgumentException
  {
    // check if array is non-null
    if (data == null)
    {
      throw new IllegalArgumentException("Error: Image channel array is not initialized.");
    }
    // check if array has enough entries
    int channels;
    if (monochrome)
    {
      channels = 1;
      if (data.length < 1)
      {
        throw new IllegalArgumentException("Error: Image channel " +
          "array must have at least one channel for monochrome " +
          "images.");
      }
    }
    else
    {
      channels = 3;
      if (data.length < 3)
      {
        throw new IllegalArgumentException("Error: Image channel " +
          "array must have at least three channels for color images.");
      }
    }
    // check if each channel has enough entries for the samples
    for (int i = 0; i < channels; i++)
    {
      if (data[i].length < numPixels)
      {
        throw new IllegalArgumentException("Error: Image channel #" + i +
          " is not large enough (" + numPixels + " entries required, " +
          data[i].length + " found).");
      }
    }
  }*/

  private void convertToRgb(int width, int height)
  {
    byte[] red = new byte[width];
    byte[] green = new byte[width];
    byte[] blue = new byte[width];
    int offset = 0;
    for (int y = 0; y < height; y++)
    {
      PCDYCbCrConversion.convertYccToRgb(
        data[INDEX_Y],
        data[INDEX_CB],
        data[INDEX_CR],
        offset,
        red,
        green,
        blue,
        0,
        width);
      System.arraycopy(red, 0, data[0], offset, width);
      System.arraycopy(green, 0, data[1], offset, width);
      System.arraycopy(blue, 0, data[2], offset, width);
      offset += width;
    }
  }

  private IntegerImage createImage(int width, int height)
  {
    if (monochrome)
    {
      Gray8Image image = new MemoryGray8Image(width, height);
      int offset = 0;
      for (int y = 0; y < height; y++)
      {
        for (int x = 0; x < width; x++)
        {
          image.putByteSample(0, x, y, data[0][offset++]);
        }
      }
      return image;
    }
    else
    if (performColorConversion)
    {
      RGB24Image image = new MemoryRGB24Image(width, height);
      int offset = 0;
      for (int y = 0; y < height; y++)
      {
        for (int x = 0; x < width; x++)
        {
          image.putByteSample(RGB24Image.INDEX_RED, x, y, data[0][offset]);
          image.putByteSample(RGB24Image.INDEX_GREEN, x, y, data[1][offset]);
          image.putByteSample(RGB24Image.INDEX_BLUE, x, y, data[2][offset]);
          offset++;
        }
      }
      return image;
    }
    else
    {
      return null;
    }
  }

  public String[] getFileExtensions()
  {
    return new String[] {".pcd"};
  }

  public String getFormatName()
  {
    return "Kodak Photo-CD (PCD)";
  }

  public String[] getMimeTypes()
  {
    return new String[] {"image/x-pcd"};
  }

  public boolean isLoadingSupported()
  {
    return true;
  }

  public boolean isSavingSupported()
  {
    return false;
  }

  /**
   * Attempts to load an image.
   * The codec must have been given an input stream, all other
   * parameters (do not convert color to RGB, load monochrome channel
   * only, load other resolution than default) can optionally be
   * chosen by calling the corresponding methods.
   *
   * @return loaded image
   * @throws IOException if there were reading errors
   * @throws OutOfMemoryException if there was not enough free memory
   *  available
   * @throws InvalidFileStructureException if the file seems to be a PCD
   *  stream but has logical errors in it
   * @throws WrongFileFormatException if this is not a PCD file
   */
  private void load() throws
    InvalidFileStructureException,
    IOException,
    UnsupportedTypeException,
    WrongFileFormatException
  {
    if (resolutionIndex != PCD_RESOLUTION_1 &&
        resolutionIndex != PCD_RESOLUTION_2 &&
        resolutionIndex != PCD_RESOLUTION_3)
    {
      throw new UnsupportedTypeException("Error reading PCD input " +
        "stream. Only the three lowest resolutions are supported.");
    }
    if (in == null)
    {
      throw new IllegalArgumentException("Input file is missing " +
        "(use PCDCodec.setInput(RandomAccessFile).");
    }
    if (in.length() < 16 * 1024)
    {
      throw new WrongFileFormatException("Not a PCD file.");
    }
    byte[] sector = new byte[SECTOR_SIZE];
    // read first sector; first 7 bytes must be 0xff
    in.readFully(sector);
    for (int i = 0; i < 7; i++)
    {
      if (sector[i] != -1)
      {
        throw new WrongFileFormatException("Input is not a valid PCD " +
          "file (wrong magic byte sequence).");
      }
    }
    // read second sector and check more magic bytes
    in.readFully(sector);
    for (int i = 0; i < MAGIC.length; i++)
    {
      if (sector[i] != MAGIC[i])
      {
        throw new WrongFileFormatException("Input is not a valid PCD " +
          "file (wrong magic byte sequence).");
      }
    }
    // get image orientation and resolution
    int rotationAngle = sector[0x602] & 0x03;
    int width = PCD_RESOLUTIONS[resolutionIndex][0];
    int height = PCD_RESOLUTIONS[resolutionIndex][1];
    int realWidth = width;
    int realHeight = height;
    if (rotationAngle == ROTATE_90_LEFT || rotationAngle == ROTATE_90_RIGHT)
    {
      realWidth = height;
      realHeight = width;
    }
    if (!hasBounds())
    {
      setBounds(0, 0, realWidth - 1, realHeight - 1);
    }
    // determine which uncompressed image will be loaded
    int uncompressedResolution = resolutionIndex;
    if (resolutionIndex > PCD_RESOLUTION_3)
    {
      uncompressedResolution = PCD_RESOLUTION_3;
    }
    // load uncompressed image
    data = allocateMemory();
    loadUncompressedImage(uncompressedResolution);
    // reverse color subsampling if necessary
    if (!monochrome)
    {
      ArrayScaling.scaleUp200Percent(data[INDEX_CB],
        PCD_RESOLUTIONS[uncompressedResolution][0] / 2,
        PCD_RESOLUTIONS[uncompressedResolution][1] / 2);
      ArrayScaling.scaleUp200Percent(data[INDEX_CR],
        PCD_RESOLUTIONS[uncompressedResolution][0] / 2,
        PCD_RESOLUTIONS[uncompressedResolution][1] / 2);
    }
    // TODO load higher resolution by decoding differences to uncompressed image
    // ...
    // convert to RGB color space if possible and desired
    if ((!monochrome) && performColorConversion)
    {
      convertToRgb(width, height);
    }
    // rotate the image if necessary
    rotateArrays(rotationAngle, width, height);
    // adjust width and height
    if (rotationAngle == ROTATE_90_LEFT || rotationAngle == ROTATE_90_RIGHT)
    {
      int temp = width;
      width = height;
      height = temp;
    }
    setImage(createImage(width, height));
  }

  /**
   * Loads one of the three lowest resolution images from the file.
   * First skips as many bytes as there are between the current
   * stream offset and the offset of the image in the PCD file
   * (first three images are at fixed positions).
   * Then reads the pixels from in to data.
     * <p>
   * Note that there are <code>width</code> times <code>height</code>
   * samples for Y, but only one fourth that many samples for each Cb and Cr
   * (because of the 4:1:1 subsampling of the two chroma components).
   * <p>
   * @param resolution one of PCD_RESOLUTION_1, PCD_RESOLUTION_2 or PCD_RESOLUTION_3
   * @throws an IOException if there were any reading errors
   */
  private void loadUncompressedImage(int resolution)
    throws IllegalArgumentException, IOException
  {
    if (resolution != PCD_RESOLUTION_1 &&
        resolution != PCD_RESOLUTION_2 &&
        resolution != PCD_RESOLUTION_3)
    {
      throw new IllegalArgumentException("Error loading " +
        "PCD image, only the lowest three resolutions are " +
        "uncompressed.");
    }
    in.seek(PCD_FILE_OFFSETS[resolution]);
    int fullWidth = PCD_RESOLUTIONS[resolution][0];
    int fullHeight = PCD_RESOLUTIONS[resolution][1];
    int halfWidth = fullWidth / 2;
    int halfHeight = fullHeight / 2;
    int offset1 = 0;
    int offset2 = 0;
    for (int y = 0; y < halfHeight; y++)
    {
      // read two luminance rows
      in.readFully(data[INDEX_Y], offset1, fullWidth * 2);
      offset1 += (fullWidth * 2);
      if (monochrome)
      {
        if (in.skipBytes(fullWidth) != fullWidth)
        {
          throw new IOException("Could not skip " + fullWidth +
            " bytes.");
        }
      }
      else
      {
        // read one row for each cb and cr
        in.readFully(data[INDEX_CB], offset2, halfWidth);
        in.readFully(data[INDEX_CR], offset2, halfWidth);
        offset2 += halfWidth;
      }
    }
  }

  /**
   * Checks the parameter and loads an image.
   */
  public void process() throws
    InvalidFileStructureException,
    MissingParameterException,
    OperationFailedException,
    UnsupportedTypeException,
    WrongFileFormatException
  {
    in = getRandomAccessFile();
    if (in == null)
    {
      throw new MissingParameterException("RandomAccessFile object needed in PCDCodec.");
    }
    if (getMode() != CodecMode.LOAD)
    {
      throw new UnsupportedTypeException("PCDCodec can only load images.");
    }
    try
    {
      load();
    }
    catch (IOException ioe)
    {
      throw new OperationFailedException("I/O error: " + ioe.toString());
    }
  }

  private void rotateArrays(int rotationAngle, int width, int height)
  {
    if (rotationAngle == NO_ROTATION)
    {
      return
    }
    int numPixels = width * height;
    for (int i = 0; i < numChannels; i++)
    {
      byte[] dest = new byte[numPixels];
      switch(rotationAngle)
      {
        case(ROTATE_90_LEFT):
        {
          ArrayRotation.rotate90Left(width, height, data[i], 0, dest, 0);
          break;
        }
        case(ROTATE_90_RIGHT):
        {
          ArrayRotation.rotate90Right(width, height, data[i], 0, dest, 0);
          break;
        }
        case(ROTATE_180):
        {
          ArrayRotation.rotate180(width, height, data[i], 0, dest, 0);
          break;
        }
      }
      System.arraycopy(dest, 0, data[i], 0, numPixels);
    }
  }

  /*private void scaleUp(int currentResolution)
  {
    int width = PCD_RESOLUTIONS[currentResolution][0];
    int height = PCD_RESOLUTIONS[currentResolution][1];
    ArrayScaling.scaleUp200Percent(data[INDEX_Y], width, height);
    if (!monochrome)
    {
      ArrayScaling.scaleUp200Percent(data[INDEX_CB], width, height);
      ArrayScaling.scaleUp200Percent(data[INDEX_CR], width, height);
    }
  }*/

  /**
   * Specify whether color is converted from PCD's YCbCr color space to
   * RGB color space.
   * The default is <code>true</code>, and you should only change this
   * if you really know what you are doing.
   * If you simply want the luminance (gray) channel, use
   * {@link #setMonochrome(boolean)} with <code>true</code> as parameter.
   * @param performColorConversion boolean that determines whether color conversion is applied
   */
  public void setColorConversion(boolean performColorConversion)
  {
    this.performColorConversion = performColorConversion;
  }

  public void setFile(String fileName, CodecMode codecMode) throws
    IOException,
    UnsupportedCodecModeException
  {
    if (codecMode == CodecMode.LOAD)
    {
      setRandomAccessFile(new RandomAccessFile(fileName, "r"), CodecMode.LOAD);
    }
    else
    {
      throw new UnsupportedCodecModeException("This PCD codec can only load images.");
    }
  }

  /**
   * Specifies whether the image is to be loaded as gray or color image.
   * If argument is true, only the gray channel is loaded.
   */
  public void setMonochrome(boolean monochrome)
  {
    this.monochrome = monochrome;
    if (monochrome)
    {
      numChannels = 1;
    }
    else
    {
      numChannels = 3;
    }
  }

  public void setResolutionIndex(int resolutionIndex)
  {
    this.resolutionIndex = resolutionIndex;
  }
}
TOP

Related Classes of net.sourceforge.jiu.codecs.PCDCodec

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.