All Downloads are FREE. Search and download functionalities are using the official Maven repository.

it.tidalwave.imageio.nef.NikonCaptureEditorMetadata Maven / Gradle / Ivy

The newest version!
/***********************************************************************************************************************
 *
 * jrawio - a Java(TM) Image I/O SPI Provider for Camera Raw files
 * Copyright (C) 2003-2011 by Tidalwave s.a.s.
 *
 ***********************************************************************************************************************
 *
 * 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.
 *
 ***********************************************************************************************************************
 *
 * WWW: http://jrawio.rawdarkroom.org
 * SCM: https://kenai.com/hg/jrawio~src
 *
 **********************************************************************************************************************/
package it.tidalwave.imageio.nef;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.awt.Rectangle;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import org.w3c.dom.Document;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import it.tidalwave.imageio.util.Logger;
import java.io.StringReader;
import org.xml.sax.InputSource;

/***********************************************************************************************************************
 *
 * @author  Fabrizio Giudici
 * @version $Id: NikonCaptureEditorMetadata.java,v 89379a3cf877 2011/02/28 23:54:05 fabrizio $
 *
 **********************************************************************************************************************/
public class NikonCaptureEditorMetadata
  {
    public abstract static class CropObject
      {
        @CheckForNull
        public abstract Rectangle getCrop();

        @Override
        public String toString()
          {
            return String.format("CropObject[%s]", getCrop());
          }
      }

    protected final static class CropObjectNoCrop extends CropObject
      {
        @Override
        public Rectangle getCrop()
          {
            return null;
          }
      }
    
    protected final static class CropObjectFromBinaryBlock extends CropObject
      {
        private final static int CROP_LEFT_OFFSET = 30;
        private final static int CROP_TOP_OFFSET = 38;
        private final static int CROP_RIGHT_OFFSET = 46;
        private final static int CROP_BOTTOM_OFFSET = 54;

        @Nonnull
        private final ByteBuffer cropBuffer;

        protected CropObjectFromBinaryBlock (final @Nonnull ByteBuffer cropBuffer)
          {
            this.cropBuffer = cropBuffer;
          }

        @Override
        @Nonnull
        public Rectangle getCrop()
          {
            final Rectangle crop = new Rectangle(0, 0, 0, 0);
            final double scale = 0.5;
            crop.x = (int)Math.round(cropBuffer.getDouble(CROP_LEFT_OFFSET) * scale);
            crop.y = (int)Math.round(cropBuffer.getDouble(CROP_TOP_OFFSET) * scale);
            final int right = (int)Math.round(cropBuffer.getDouble(CROP_RIGHT_OFFSET) * scale);
            final int bottom = (int)Math.round(cropBuffer.getDouble(CROP_BOTTOM_OFFSET) * scale);
            crop.width = right - crop.x - 1 + 1;
            crop.height = bottom - crop.y - 1 + 1;

            return crop;
          }
      }

    protected final static class CropObjectFromXMLBlock extends CropObject
      {
        private final static  XPath XPATH = XPathFactory.newInstance().newXPath();
        
        private final static XPathExpression ACTIVE_EXPR;
        private final static XPathExpression CROP_LEFT_EXPR;
        private final static XPathExpression CROP_TOP_EXPR;
        private final static XPathExpression CROP_RIGHT_EXPR;
        private final static XPathExpression CROP_BOTTOM_EXPR;

        @Nonnull
        private final Document document;

        static
          {
            try
              {
                ACTIVE_EXPR = XPATH.compile("/historystep/filter[@id='nik::Crop']/active/text()");
                CROP_LEFT_EXPR = XPATH.compile("/historystep/filter[@id='nik::Crop']/parameters/point[@name='cropStart']/@x");
                CROP_TOP_EXPR = XPATH.compile("/historystep/filter[@id='nik::Crop']/parameters/point[@name='cropStart']/@y");
                CROP_RIGHT_EXPR = XPATH.compile("/historystep/filter[@id='nik::Crop']/parameters/point[@name='cropEnd']/@x");
                CROP_BOTTOM_EXPR = XPATH.compile("/historystep/filter[@id='nik::Crop']/parameters/point[@name='cropEnd']/@y");
              }
            catch (XPathExpressionException e)
              {
                throw new ExceptionInInitializerError(e);
              }
          }
        
        protected CropObjectFromXMLBlock (final @Nonnull Document document)
          {
            this.document = document;
          }

        @CheckForNull
        public Rectangle getCrop()
          {
            try
              {
                if (!Boolean.valueOf(ACTIVE_EXPR.evaluate(document)))
                  {
                    return null;
                  }

                final int cropLeft = Integer.valueOf(CROP_LEFT_EXPR.evaluate(document));
                final int cropTop = Integer.valueOf(CROP_TOP_EXPR.evaluate(document));
                final int cropRight = Integer.valueOf(CROP_RIGHT_EXPR.evaluate(document));
                final int cropBottom = Integer.valueOf(CROP_BOTTOM_EXPR.evaluate(document));

                final Rectangle crop = new Rectangle(0, 0, 0, 0);
                crop.x = cropLeft;
                crop.y = cropTop;
                crop.width = cropRight - cropLeft + 1 - 1;
                crop.height = cropBottom - cropTop + 1 - 1;

                return crop;
              }
            catch (XPathExpressionException e)
              {
                throw new RuntimeException(e);
              }
          }
      }

    public static class UnsharpMaskData
      {
        public final static int RGB = 0;

        public final static int RED = 1;

        public final static int GREEN = 2;

        public final static int BLUE = 3;

        public final static int YELLOW = 4;

        public final static int MAGENTA = 5;

        public final static int CYAN = 6;

        private int type;

        private int intensity;

        private int haloWidth;

        private int threshold;

        public UnsharpMaskData (final int type, final int intensity, final int haloWidth, final int threshold)
          {
            this.type = type;
            this.intensity = intensity;
            this.haloWidth = haloWidth;
            this.threshold = threshold;
          }

        public int getHaloWidth()
          {
            return haloWidth;
          }

        public int getIntensity()
          {
            return intensity;
          }

        public int getThreshold()
          {
            return threshold;
          }

        public int getType()
          {
            return type;
          }
      }

    private final static String CLASS = NikonCaptureEditorMetadata.class.getName();
    private final static Logger logger = Logger.getLogger(CLASS);

    private static final int NOISE_REDUCTION_MARKER = 0x753dcbc0;

    private static final int NOISE_REDUCTION_DATA_MARKER = 0x926f13e0;

    private static final int WHITE_BALANCE_MARKER = 0x76a43204;

    private static final int WHITE_BALANCE_DATA_MARKER = 0xbf3c6c20;

    private static final int ADVANCED_RAW_MARKER = 0x76a43203;

    private static final int ADVANCED_RAW_DATA_MARKER = 0x56a54260;

    private static final int COLOR_BOOSTER_MARKER = 0x5f0e7d23;

    private static final int COLOR_BOOSTER_DATA_MARKER = 0xb999a36f;

    private static final int UNSHARP_MASK_MARKER = 0x76a43200;

    private static final int UNSHARP_MASK_DATA_MARKER = 0xe42b5161;

    private static final int CROP_MARKER = 0x374233e0;

    private static final int ORIENTATION_MARKER = 0x76a43207;

    private static final int VIGNETTE_MARKER = 0x76a43205;

    private static final int COLOR_BALANCE_MARKER = 0x76a43202;

    private static final int CURVES_MARKER = 0x76a43201;

    private static final int DUST_REFERENCE_MARKER = 0xf852757d;

    private static final int PHOTO_EFFECTS_MARKER = 0xab5eca5e;

    private static final int PHOTO_EFFECTS_DATA_MARKER = 0xb0384e1e;

    private static final int HISTORY_STEP = 0xe9651831;

    private static final int XML_DATA_MARKER = 0x083a1a25;

    private static Map colorModeMap = new HashMap();

    private static Map hueMap = new HashMap();

    private static Map sharpeningMap = new HashMap();

    private static Map white1Map = new HashMap();

    private static Map white2Map = new HashMap();

    private static Map toneCompMap = new HashMap();

    private static Map colorBoosterMap = new HashMap();

    private static Map moireReductionMap = new HashMap();

    private static Map photoEffectMap = new HashMap();

    private static Map unsharpMaskMap = new HashMap();

    private static Map markerMap = new HashMap();

    //// ORIENTATION ///////////////////////////////////////////////////////////////
    public final static int ORIENTATION_0 = 0;

    public final static int ORIENTATION_CW90 = 90;

    public final static int ORIENTATION_CCW90 = 270;

    public final static int ORIENTATION_180 = 180;

    private final static int ORIENTATION_ORIENTATION_OFFSET = 0;

    //// ADVANCED RAW //////////////////////////////////////////////////////////////
    private final static int ADVANCED_RAW_ENABLED_OFFSET = 0;

    //// ADVANCED RAW DATA /////////////////////////////////////////////////////////
    private final static int ADVANCED_RAW_EV_OVERRIDE_OFFSET = 0;

    private final static int ADVANCED_RAW_CONTRAST_OFFSET = 2;

    private final static int ADVANCED_RAW_COLOR_MODE_OFFSET = 4;

    private final static int ADVANCED_RAW_HUE_CORRECTION_ENABLED_OFFSET = 6;

    private final static int ADVANCED_RAW_HUE_CORRECTION_OFFSET = 7;

    private final static int ADVANCED_RAW_SATURATION_ENABLED_OFFSET = 13;

    private final static int ADVANCED_RAW_SATURATION_COMPENSATION_OFFSET = 14;

    private final static int ADVANCED_RAW_SHARPENING_OFFSET = 15;

    public final static int ADVANCED_RAW_TONECOMP_UNCHANGED_CONTRAST = 0x00;

    public final static int ADVANCED_RAW_TONECOMP_NORMAL_CONTRAST = 0x01;

    public final static int ADVANCED_RAW_TONECOMP_LOW_CONTRAST = 0x02;

    public final static int ADVANCED_RAW_TONECOMP_HIGH_CONTRAST = 0x03;

    public final static int ADVANCED_RAW_TONECOMP_USER_DEFINED_CURVE = 0x04;

    public final static int ADVANCED_RAW_TONECOMP_MEDIUM_LOW_CONTRAST = 0x05;

    public final static int ADVANCED_RAW_TONECOMP_MEDIUM_HIGH_CONTRAST = 0x06;

    public final static int ADVANCED_RAW_COLOR_MODE_UNCHANGED = 0x00;

    public final static int ADVANCED_RAW_COLOR_MODE_I = 0x01;

    public final static int ADVANCED_RAW_COLOR_MODE_II = 0x02;

    public final static int ADVANCED_RAW_COLOR_MODE_III = 0x04;

    public final static int ADVANCED_RAW_HUE_MINUS_9 = 0x00;

    public final static int ADVANCED_RAW_HUE_MINUS_6 = 0x01;

    public final static int ADVANCED_RAW_HUE_MINUS_3 = 0x02;

    public final static int ADVANCED_RAW_HUE_ZERO = 0x03;

    public final static int ADVANCED_RAW_HUE_PLUS_3 = 0x04;

    public final static int ADVANCED_RAW_HUE_PLUS_6 = 0x05;

    public final static int ADVANCED_RAW_HUE_PLUS_9 = 0x06;

    public final static int ADVANCED_RAW_SHARPENING_UNCHANGED = 0x00;

    public final static int ADVANCED_RAW_SHARPENING_NONE = 0x01;

    public final static int ADVANCED_RAW_SHARPENING_LOW = 0x02;

    public final static int ADVANCED_RAW_SHARPENING_NORMAL = 0x03;

    public final static int ADVANCED_RAW_SHARPENING_HIGH = 0x04;

    //// WHITE BALANCE /////////////////////////////////////////////////////////////
    public final static int WHITE_BALANCE_USE_GRAY_POINT = 0x0001;

    public final static int WHITE_BALANCE_RECORDED_VALUE = 0x0002;

    public final static int WHITE_BALANCE_SET_TEMPERATURE = 0x0003;

    public final static int WHITE_BALANCE_CALCULATE_AUTOMATICALLY = 0x0004;

    public final static int WHITE_BALANCE_DAYLIGHT_DIRECT_SUNLIGHT = 0x0200;

    public final static int WHITE_BALANCE_DAYLIGHT_CLOUDY = 0x0202;

    public final static int WHITE_BALANCE_DAYLIGHT_SHADE = 0x0201;

    public final static int WHITE_BALANCE_STANDARD_FLUO_WARM_WHITE = 0x0300;

    public final static int WHITE_BALANCE_STANDARD_FLUO_3700K = 0x0301;

    public final static int WHITE_BALANCE_STANDARD_FLUO_COOL_WHITE = 0x0302;

    public final static int WHITE_BALANCE_STANDARD_FLUO_5000K = 0x0303;

    public final static int WHITE_BALANCE_STANDARD_FLUO_DAYLIGHT = 0x0304;

    public final static int WHITE_BALANCE_HICOLOR_FLUO_WARM_WHITE = 0x0400;

    public final static int WHITE_BALANCE_HICOLOR_FLUO_3700K = 0x0401;

    public final static int WHITE_BALANCE_HICOLOR_FLUO_COOL_WHITE = 0x0402;

    public final static int WHITE_BALANCE_HICOLOR_FLUO_5000K = 0x0403;

    public final static int WHITE_BALANCE_HICOLOR_FLUO_DAYLIGHT = 0x0404;

    public final static int WHITE_BALANCE_FLASH = 0x0500;

    private static final int WHITE_BALANCE_INCANDESCENT = 256;

    private final static int WHITE_BALANCE_ENABLED_OFFSET = 0;

    //// WHITE BALANCE DATA ////////////////////////////////////////////////////////
    private final static int WHITE_BALANCE_RED_COEFF_OFFSET = 0;

    private final static int WHITE_BALANCE_BLUE_COEFF_OFFSET = 8;

    private final static int WHITE_BALANCE_WHITEPOINT_OFFSET = 16;

    private final static int WHITE_BALANCE_WHITEPOINT_FINE_OFFSET = 20;

    private final static int WHITE_BALANCE_TEMPERATURE_OFFSET = 24;

    //// COLOR BOOSTER /////////////////////////////////////////////////////////////
    private final static int COLOR_BOOSTER_ENABLED_OFFSET = 0;

    private final static int COLOR_BOOSTER_TYPE_OFFSET = 0;

    private final static int COLOR_BOOSTER_LEVEL_OFFSET = 1;

    public final static int COLOR_BOOSTER_NATURE = 0;

    public final static int COLOR_BOOSTER_PEOPLE = 1;

    //// NOISE REDUCTION ///////////////////////////////////////////////////////////
    private final static int NOISE_REDUCTION_ENABLED_OFFSET = 0;

    private final static int NOISE_REDUCTION_OFFSET = 0;

    private final static int NOISE_REDUCTION_EDGE_OFFSET = 4;

    private final static int NOISE_REDUCTION_MOIRE_OFFSET = 5;

    public final static int NOISE_REDUCTION_MOIRE_OFF = 0;

    public final static int NOISE_REDUCTION_MOIRE_LOW = 1;

    public final static int NOISE_REDUCTION_MOIRE_MEDIUM = 2;

    public final static int NOISE_REDUCTION_MOIRE_HIGH = 3;

    /// PHOTO EFFECTS DATA /////////////////////////////////////////////////////////
    private final static int PHOTO_EFFECTS_EFFECT_OFFSET = 0;

    private final static int PHOTO_EFFECTS_CYAN_RED_BALANCE_OFFSET = 4;

    private final static int PHOTO_EFFECTS_MAGENTA_GREEN_BALANCE_OFFSET = 6;

    private final static int PHOTO_EFFECTS_YELLOW_BLUE_BALANCE_OFFSET = 8;

    public final static int PHOTO_EFFECT_NONE = 0;

    public final static int PHOTO_EFFECT_BW = 1;

    public final static int PHOTO_EFFECT_SEPIA = 2;

    public final static int PHOTO_EFFECT_TINTED = 3;

    /// UNSHARP MASK DATA //////////////////////////////////////////////////////////
    private static final int UNSHARP_MASK_TYPE_OFFSET = 19;

    private static final int UNSHARP_MASK_INTENSITY_OFFSET = 23;

    private static final int UNSHARP_MASK_HALO_WIDTH_OFFSET = 25;

    private static final int UNSHARP_MASK_THRESHOLD_OFFSET = 27;

    private static final int UNSHARP_MASK_DELTA = (46 - 19);

    static
      {
        registerConstant(colorModeMap, ADVANCED_RAW_COLOR_MODE_UNCHANGED, "unchanged");
        registerConstant(colorModeMap, ADVANCED_RAW_COLOR_MODE_I, "mode I (sRGB)");
        registerConstant(colorModeMap, ADVANCED_RAW_COLOR_MODE_II, "mode II (Adobe RGB)");
        registerConstant(colorModeMap, ADVANCED_RAW_COLOR_MODE_III, "mode III (sRGB)");

        registerConstant(hueMap, ADVANCED_RAW_HUE_MINUS_9, "-9");
        registerConstant(hueMap, ADVANCED_RAW_HUE_MINUS_6, "-6");
        registerConstant(hueMap, ADVANCED_RAW_HUE_MINUS_3, "-3");
        registerConstant(hueMap, ADVANCED_RAW_HUE_ZERO, "none");
        registerConstant(hueMap, ADVANCED_RAW_HUE_PLUS_3, "+3");
        registerConstant(hueMap, ADVANCED_RAW_HUE_PLUS_6, "+6");
        registerConstant(hueMap, ADVANCED_RAW_HUE_PLUS_9, "+9");

        registerConstant(toneCompMap, ADVANCED_RAW_TONECOMP_UNCHANGED_CONTRAST, "unchanged");
        registerConstant(toneCompMap, ADVANCED_RAW_TONECOMP_LOW_CONTRAST, "low");
        registerConstant(toneCompMap, ADVANCED_RAW_TONECOMP_NORMAL_CONTRAST, "normal");
        registerConstant(toneCompMap, ADVANCED_RAW_TONECOMP_HIGH_CONTRAST, "high");
        registerConstant(toneCompMap, ADVANCED_RAW_TONECOMP_USER_DEFINED_CURVE, "user defined curve");
        registerConstant(toneCompMap, ADVANCED_RAW_TONECOMP_MEDIUM_LOW_CONTRAST, "medium low");
        registerConstant(toneCompMap, ADVANCED_RAW_TONECOMP_MEDIUM_HIGH_CONTRAST, "medium high");

        registerConstant(sharpeningMap, ADVANCED_RAW_SHARPENING_UNCHANGED, "unchanged");
        registerConstant(sharpeningMap, ADVANCED_RAW_SHARPENING_NONE, "none");
        registerConstant(sharpeningMap, ADVANCED_RAW_SHARPENING_LOW, "low");
        registerConstant(sharpeningMap, ADVANCED_RAW_SHARPENING_NORMAL, "normal");
        registerConstant(sharpeningMap, ADVANCED_RAW_SHARPENING_HIGH, "high");

        registerConstant(white1Map, WHITE_BALANCE_USE_GRAY_POINT, "use gray point");
        registerConstant(white1Map, WHITE_BALANCE_RECORDED_VALUE, "recorded value");
        registerConstant(white1Map, WHITE_BALANCE_SET_TEMPERATURE, "set");
        registerConstant(white1Map, WHITE_BALANCE_CALCULATE_AUTOMATICALLY, "auto");

        registerConstant(white2Map, WHITE_BALANCE_INCANDESCENT, "incandescent");
        registerConstant(white2Map, WHITE_BALANCE_DAYLIGHT_DIRECT_SUNLIGHT, "daylight/direct sunlight");
        registerConstant(white2Map, WHITE_BALANCE_DAYLIGHT_CLOUDY, "daylight/cloudy");
        registerConstant(white2Map, WHITE_BALANCE_DAYLIGHT_SHADE, "daylight/shade");
        registerConstant(white2Map, WHITE_BALANCE_STANDARD_FLUO_WARM_WHITE, "standard fluorescent/warm white");
        registerConstant(white2Map, WHITE_BALANCE_STANDARD_FLUO_3700K, "standard fluorescent/3700K");
        registerConstant(white2Map, WHITE_BALANCE_STANDARD_FLUO_COOL_WHITE, "standard fluorescent/cool white");
        registerConstant(white2Map, WHITE_BALANCE_STANDARD_FLUO_5000K, "standard fluorescent/5000K");
        registerConstant(white2Map, WHITE_BALANCE_STANDARD_FLUO_DAYLIGHT, "standard fluorescent/daylight");
        registerConstant(white2Map, WHITE_BALANCE_HICOLOR_FLUO_WARM_WHITE, "hicolor fluorescent/warm white");
        registerConstant(white2Map, WHITE_BALANCE_HICOLOR_FLUO_3700K, "hicolor fluorescent/3700K");
        registerConstant(white2Map, WHITE_BALANCE_HICOLOR_FLUO_COOL_WHITE, "hicolor fluorescent/cool white");
        registerConstant(white2Map, WHITE_BALANCE_HICOLOR_FLUO_5000K, "hicolor fluorescent/5000K");
        registerConstant(white2Map, WHITE_BALANCE_HICOLOR_FLUO_DAYLIGHT, "hicolor fluorescent/daylight");
        registerConstant(white2Map, WHITE_BALANCE_FLASH, "flash");

        registerConstant(colorBoosterMap, COLOR_BOOSTER_NATURE, "nature");
        registerConstant(colorBoosterMap, COLOR_BOOSTER_PEOPLE, "people");

        registerConstant(moireReductionMap, NOISE_REDUCTION_MOIRE_OFF, "off");
        registerConstant(moireReductionMap, NOISE_REDUCTION_MOIRE_LOW, "low");
        registerConstant(moireReductionMap, NOISE_REDUCTION_MOIRE_MEDIUM, "medium");
        registerConstant(moireReductionMap, NOISE_REDUCTION_MOIRE_HIGH, "high");

        registerConstant(photoEffectMap, PHOTO_EFFECT_NONE, "none");
        registerConstant(photoEffectMap, PHOTO_EFFECT_BW, "black & white");
        registerConstant(photoEffectMap, PHOTO_EFFECT_SEPIA, "sepia");
        registerConstant(photoEffectMap, PHOTO_EFFECT_TINTED, "tinted");

        registerConstant(unsharpMaskMap, UnsharpMaskData.RGB, "RGB");
        registerConstant(unsharpMaskMap, UnsharpMaskData.RED, "RED");
        registerConstant(unsharpMaskMap, UnsharpMaskData.GREEN, "GREEN");
        registerConstant(unsharpMaskMap, UnsharpMaskData.BLUE, "BLUE");
        registerConstant(unsharpMaskMap, UnsharpMaskData.YELLOW, "YELLOW");
        registerConstant(unsharpMaskMap, UnsharpMaskData.MAGENTA, "MAGENTA");
        registerConstant(unsharpMaskMap, UnsharpMaskData.CYAN, "CYAN");

        registerConstant(markerMap, NOISE_REDUCTION_MARKER, "NoiseReduction");
        registerConstant(markerMap, NOISE_REDUCTION_DATA_MARKER, "NoiseReductionData");
        registerConstant(markerMap, WHITE_BALANCE_MARKER, "WhiteBalance");
        registerConstant(markerMap, WHITE_BALANCE_DATA_MARKER, "WhiteBalanceData");
        registerConstant(markerMap, ADVANCED_RAW_MARKER, "AdvancedRaw");
        registerConstant(markerMap, ADVANCED_RAW_DATA_MARKER, "AdvancedRawData");
        registerConstant(markerMap, COLOR_BOOSTER_MARKER, "ColorBooster");
        registerConstant(markerMap, COLOR_BOOSTER_DATA_MARKER, "ColorBoosterData");
        registerConstant(markerMap, UNSHARP_MASK_MARKER, "UnsharpMask");
        registerConstant(markerMap, UNSHARP_MASK_DATA_MARKER, "UnsharpMaskData");
        registerConstant(markerMap, CROP_MARKER, "Crop");
        registerConstant(markerMap, ORIENTATION_MARKER, "Orientation");
        registerConstant(markerMap, VIGNETTE_MARKER, "Vignette");
        registerConstant(markerMap, COLOR_BALANCE_MARKER, "ColorBalance");
        registerConstant(markerMap, CURVES_MARKER, "Curves");
        registerConstant(markerMap, DUST_REFERENCE_MARKER, "DustReference");
        registerConstant(markerMap, PHOTO_EFFECTS_MARKER, "PhotoEffects");
        registerConstant(markerMap, PHOTO_EFFECTS_DATA_MARKER, "PhotoEffectsData");
      }

    private ByteBuffer byteBuffer;

    private ByteBuffer noiseReductionBuffer;

    private ByteBuffer noiseReductionDataBuffer;

    private ByteBuffer advancedRawBuffer;

    private ByteBuffer advancedRawDataBuffer;

    private ByteBuffer whiteBalanceBuffer;

    private ByteBuffer whiteBalanceDataBuffer;

    private ByteBuffer colorBoosterBuffer;

    private ByteBuffer colorBoosterDataBuffer;

    private ByteBuffer unsharpMaskBuffer;

    private ByteBuffer unsharpMaskDataBuffer;

    private ByteBuffer cropBuffer;

    private ByteBuffer orientationBuffer;

    private ByteBuffer curvesBuffer;

    private ByteBuffer colorBalanceBuffer;

    private ByteBuffer vignetteBuffer;

    private ByteBuffer photoEffectBuffer;

    private ByteBuffer photoEffectDataBuffer;

    private Document xmlData;

    private Document historyStep;

    private Map bufferMapById = new HashMap();

    @Nonnull
    private CropObject cropObject = new CropObjectNoCrop();

    /*******************************************************************************************************************
     * 
     * @param buffer
     * 
     *******************************************************************************/
    public NikonCaptureEditorMetadata (final @Nonnull byte[] buffer) 
      {
        logger.finer("NikonCaptureEditorMetadata(%d bytes)", buffer.length);

        final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

        byteBuffer = ByteBuffer.allocate(buffer.length);
        byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // FIXME check this
        byteBuffer.put(buffer);

        int offset = 0;

        final int masterSize = (int)byteBuffer.getLong(offset + 18);
        offset += 22; // FIXME: parse the header in a better way!
        logger.finest(">>>> master size: %d", masterSize);

        // TODO: we stop after masterSize, but often there further data beyond that point; garbage or other tags?
        while (offset + 22 < masterSize)
          {
            final int id = byteBuffer.getInt(offset);
            int size = (int)byteBuffer.getLong(offset + 18);
            offset += 22;
            byteBuffer.position(offset);
            logger.finest(">>>> read subbuffer id: 0x%x, size: %d", id, size);

            // This was a safety check, that *seems* to be no more needed since fixing JRW-276 - keeping it for safety.
            if (size <= 0)
              {
                logger.warning("Giving up with subbuffer id: 0x%x, because of size %d <= 0", id, size);
                break;
              }

            final ByteBuffer subBuffer = byteBuffer.slice();
            subBuffer.order(byteBuffer.order());
            //
            // For all block but XML_DATA, the size includes the 4-bytes that define size itself.
            //
            if (id != XML_DATA_MARKER)
              {
                size -= 4;
              }

            subBuffer.limit(size); // we're skipping the first 4 bytes that are the size
            bufferMapById.put(id, subBuffer);
            
            final String s = logger.isLoggable(Level.FINER) ? getDebugString(offset, id, size, subBuffer) : "";
            offset += size;

            switch (id)
              {
                case UNSHARP_MASK_MARKER:
                  unsharpMaskBuffer = subBuffer;
                  logger.finer(">>>> UNSHARP MASK: %s", s);
                  break;

                case UNSHARP_MASK_DATA_MARKER:
                  unsharpMaskDataBuffer = subBuffer;
                  logger.finer(">>>> UNSHARP MASK DATA: %s", s);
                  break;

                case CURVES_MARKER:
                  curvesBuffer = subBuffer;
                  logger.finer(">>>> CURVES: %s", s);
                  break;

                case COLOR_BALANCE_MARKER:
                  colorBalanceBuffer = subBuffer;
                  logger.finer(">>>> COLOR BALANCE: %s", s);
                  break;

                case ADVANCED_RAW_MARKER:
                  advancedRawBuffer = subBuffer;
                  logger.finer(">>>> ADVANCED RAW: %s", s);
                  break;

                case ADVANCED_RAW_DATA_MARKER:
                  advancedRawDataBuffer = subBuffer;
                  logger.finer(">>>> ADVANCED RAW DATA: %s", s);
                  break;

                case WHITE_BALANCE_MARKER:
                  whiteBalanceBuffer = subBuffer;
                  logger.finer(">>>> WHITE BALANCE: %s", s);
                  break;

                case WHITE_BALANCE_DATA_MARKER:
                  whiteBalanceDataBuffer = subBuffer;
                  logger.finer(">>>> WHITE BALANCE DATA: %s", s);
                  break;

                case VIGNETTE_MARKER:
                  vignetteBuffer = subBuffer;
                  logger.finer(">>>> VIGNETTE: %s", s);
                  break;

                case ORIENTATION_MARKER:
                  orientationBuffer = subBuffer;
                  logger.finer(">>>> ORIENTATION: %s", s);
                  break;

                case CROP_MARKER:
                  cropBuffer = subBuffer;
                  cropObject = new CropObjectFromBinaryBlock(cropBuffer);
                  logger.finer(">>>> CROP: " + s);
                  break;

                case NOISE_REDUCTION_MARKER:
                  noiseReductionBuffer = subBuffer;
                  logger.finer(">>>> NOISE REDUCTION: %s", s);
                  break;

                case NOISE_REDUCTION_DATA_MARKER:
                  noiseReductionDataBuffer = subBuffer;
                  logger.finer(">>>> NOISE REDUCTION DATA: %s", s);
                  break;

                case COLOR_BOOSTER_MARKER:
                  colorBoosterBuffer = subBuffer;
                  logger.finer(">>>> COLOR BOOSTER: %s", s);
                  break;

                case COLOR_BOOSTER_DATA_MARKER:
                  colorBoosterDataBuffer = subBuffer;
                  logger.finer(">>>> COLOR BOOSTER DATA: %s", s);
                  break;

                case DUST_REFERENCE_MARKER:
                  logger.finer(">>>> DUST REFERENCE: %s", s);
                  break;

                case PHOTO_EFFECTS_MARKER:
                  photoEffectBuffer = subBuffer;
                  logger.finer(">>>> PHOTO EFFECTS: %s", s);
                  break;

                case PHOTO_EFFECTS_DATA_MARKER:
                  photoEffectDataBuffer = subBuffer;
                  logger.finer(">>>> PHOTO EFFECTS DATA: %s", s);
                  break;

                case HISTORY_STEP:
                  final String historyStepAsString = toString(subBuffer);
                  logger.finer(">>>> HISTORY STEP : %s", historyStepAsString);

                  if ("".equals(historyStepAsString.trim()))
                    {
                      cropObject = new CropObjectNoCrop(); // must override the possibly set CropObjectFromBinaryBlock
                    }
                  else
                    {
                      try
                        {
                          final DocumentBuilder db = dbf.newDocumentBuilder();
                          historyStep = db.parse(new InputSource(new StringReader(historyStepAsString)));
                          cropObject = new CropObjectFromXMLBlock(historyStep);
                        }
                      catch (Exception e)
                        {
                          logger.severe("Cannot parse XML HISTORY STEP: %s", e.getMessage());
                          logger.throwing(CLASS, "ctor", e);
                        }
                    }
                  break;

                case XML_DATA_MARKER:
                  // Sounds as Nikon doesn't ever know what a valid XML document is... we must fake a root element.
                  final String xmlDataAsString = "" + toString(subBuffer) + "";
                  logger.finer(">>>> XML DATA: %s", xmlDataAsString);

                  try
                    {
                      final DocumentBuilder db = dbf.newDocumentBuilder();
                      xmlData = db.parse(new InputSource(new StringReader(xmlDataAsString)));
                    }
                  catch (Exception e)
                    {
                      logger.severe("Cannot parse XML DATA: %s", e.getMessage());
                      logger.throwing(CLASS, "ctor", e);
                    }
                  break;

                default:
                  logger.finer(">>>> UNKNOWN: " + s);
                  break;
              }
          }

        logger.finer(">>>> %s", toString());
      }

    /*******************************************************************************************************************
     * 
     * @param id
     * @return
     * 
     *******************************************************************************/
    @CheckForNull
    public ByteBuffer getBuffer (final @Nonnull Integer id)
      {
        return (ByteBuffer)bufferMapById.get(id);
      }

    /*******************************************************************************************************************
     * 
     * @param id
     * @return
     * 
     *******************************************************************************/
    @CheckForNull
    public static String getBufferName (final @Nonnull Integer id)
      {
        return getConstant(markerMap, id.intValue());
      }

    /*******************************************************************************************************************
     * 
     * @return
     * 
     *******************************************************************************/
    @Nonnull
    public Set bufferIdSet()
      {
        return Collections.unmodifiableSet(bufferMapById.keySet());
      }

    public int getOrientation()
      {
        return orientationBuffer.getShort(ORIENTATION_ORIENTATION_OFFSET);
      }

    //    private final static int CROP_LEFT_PREV_OFFSET    = 62;
    //    private final static int CROP_TOP_PREV_OFFSET     = 70;
    //    private final static int CROP_RIGHT_PREV_OFFSET   = 78;
    //    private final static int CROP_BOTTOM_PREV_OFFSET  = 86;
    //    private final static int CROP_XDPI_OFFSET         = 158;
    //    private final static int CROP_XDPI_FACTOR_OFFSET  = 166;
    //    private final static int CROP_YDPI_OFFSET         = 182;
    //    private final static int CROP_YDPI_FACTOR_OFFSET  = 190;
    //    private final static int CROP_PIXEL_WIDTH_OFFSET  = 198;
    //    private final static int CROP_PIXEL_HEIGHT_OFFSET = 206;
    //    private final static int CROP_PIXEL_AREA_OFFSET   = 214;
    //    private final static int CROP_RATIO_OFFSET        = 222;

    @Nonnull
    public CropObject getCropObject()
      {
        return cropObject;
      }

    public boolean isAdvancedRawEnabled()
      {
        return (advancedRawBuffer != null) && (advancedRawBuffer.get(ADVANCED_RAW_ENABLED_OFFSET) != 0);
      }

    public int getEVCompensation()
      {
        return advancedRawDataBuffer.getShort(ADVANCED_RAW_EV_OVERRIDE_OFFSET);
      }

    public int getToneCompensation()
      {
        return advancedRawDataBuffer.getShort(ADVANCED_RAW_CONTRAST_OFFSET);
      }

    public int getColorMode()
      {
        return advancedRawDataBuffer.getShort(ADVANCED_RAW_COLOR_MODE_OFFSET);
      }

    public boolean isHueCorrectionEnabled()
      {
        return advancedRawDataBuffer.get(ADVANCED_RAW_HUE_CORRECTION_ENABLED_OFFSET) != 0;
      }

    public int getHueCorrection()
      {
        return advancedRawDataBuffer.get(ADVANCED_RAW_HUE_CORRECTION_OFFSET);
      }

    public boolean isSaturationCompensationEnabled()
      {
        return advancedRawDataBuffer.get(ADVANCED_RAW_SATURATION_ENABLED_OFFSET) != 0;
      }

    public int getSaturationCompensation()
      {
        return advancedRawDataBuffer.get(ADVANCED_RAW_SATURATION_COMPENSATION_OFFSET);
      }

    public int getSharpening()
      {
        return advancedRawDataBuffer.get(ADVANCED_RAW_SHARPENING_OFFSET);
      }

    public boolean isWhiteBalanceEnabled()
      {
        return (whiteBalanceBuffer != null) && (whiteBalanceBuffer.get(WHITE_BALANCE_ENABLED_OFFSET) == 0x01);
      }

    @Nonnegative
    public double getWhiteBalanceRedCoeff()
      {
        return whiteBalanceDataBuffer.getDouble(WHITE_BALANCE_RED_COEFF_OFFSET);
      }

    @Nonnegative
    public double getWhiteBalanceBlueCoeff()
      {
        return whiteBalanceDataBuffer.getDouble(WHITE_BALANCE_BLUE_COEFF_OFFSET);
      }

    public int getWhiteBalanceWhitePoint()
      {
        return whiteBalanceDataBuffer.getInt(WHITE_BALANCE_WHITEPOINT_OFFSET);
      }

    public int getWhiteBalanceWhitePointFine()
      {
        return whiteBalanceDataBuffer.getInt(WHITE_BALANCE_WHITEPOINT_FINE_OFFSET);
      }

    @Nonnegative
    public int getWhiteBalanceTemperature()
      {
        return whiteBalanceDataBuffer.getInt(WHITE_BALANCE_TEMPERATURE_OFFSET);
      }

    public boolean isColorBoosterEnabled()
      {
        return (colorBoosterBuffer != null) && (colorBoosterBuffer.get(COLOR_BOOSTER_ENABLED_OFFSET) == 0x01);
      }

    public int getColorBoosterType()
      {
        return colorBoosterDataBuffer.get(COLOR_BOOSTER_TYPE_OFFSET);
      }

    public int getColorBoosterLevel()
      {
        return colorBoosterDataBuffer.getInt(COLOR_BOOSTER_LEVEL_OFFSET);
      }

    public boolean isNoiseReductionEnabled()
      {
        return (noiseReductionBuffer != null) && (noiseReductionBuffer.get(NOISE_REDUCTION_ENABLED_OFFSET) == 0x01);
      }

    public int getNoiseReduction()
      {
        return noiseReductionDataBuffer.getInt(NOISE_REDUCTION_OFFSET);
      }

    public boolean isEdgeNoiseReductionEnabled()
      {
        return (noiseReductionDataBuffer.limit() > NOISE_REDUCTION_EDGE_OFFSET)
            && (noiseReductionDataBuffer.get(NOISE_REDUCTION_EDGE_OFFSET) == 0x01);
      }

    public int getMoireReduction()
      {
        return (noiseReductionDataBuffer.limit() > NOISE_REDUCTION_MOIRE_OFFSET) ? noiseReductionDataBuffer
            .getInt(NOISE_REDUCTION_MOIRE_OFFSET) : NOISE_REDUCTION_MOIRE_OFF;
      }

    public boolean isPhotoEffectEnabled()
      {
        return (photoEffectBuffer != null) && (photoEffectDataBuffer != null) && (photoEffectBuffer.get(0x00) == 1);
      }

    public int getPhotoEffect()
      {
        return photoEffectDataBuffer.getShort(PHOTO_EFFECTS_EFFECT_OFFSET);
      }

    public int getCyanRedBalance()
      {
        return photoEffectDataBuffer.getShort(PHOTO_EFFECTS_CYAN_RED_BALANCE_OFFSET);
      }

    public int getMagentaGreenBalance()
      {
        return photoEffectDataBuffer.getShort(PHOTO_EFFECTS_MAGENTA_GREEN_BALANCE_OFFSET);
      }

    public int getYellowBlueBalance()
      {
        return photoEffectDataBuffer.getShort(PHOTO_EFFECTS_YELLOW_BLUE_BALANCE_OFFSET);
      }

    public boolean isUnsharpMaskEnabled()
      {
        return getUnsharpMaskData() != null; // unsharpMaskBuffer.get(0x00) == 1; FIXME
      }

    @CheckForNull
    public UnsharpMaskData[] getUnsharpMaskData()
      {
        if (unsharpMaskDataBuffer.limit() < 19)
          {
            return null;
          }

        int count = 1 + (unsharpMaskDataBuffer.limit() - 19) / UNSHARP_MASK_DELTA;
        UnsharpMaskData[] unsharpMaskData = new UnsharpMaskData[count];

        for (int i = 0; i < count; i++)
          {
            int type = unsharpMaskDataBuffer.get(UNSHARP_MASK_TYPE_OFFSET + UNSHARP_MASK_DELTA * i);
            int intensity = unsharpMaskDataBuffer.getShort(UNSHARP_MASK_INTENSITY_OFFSET + UNSHARP_MASK_DELTA * i);
            int haloWidth = unsharpMaskDataBuffer.getShort(UNSHARP_MASK_HALO_WIDTH_OFFSET + UNSHARP_MASK_DELTA * i);
            int threshold = unsharpMaskDataBuffer.get(UNSHARP_MASK_THRESHOLD_OFFSET + UNSHARP_MASK_DELTA * i) & 0xff;
            unsharpMaskData[i] = new UnsharpMaskData(type, intensity, haloWidth, threshold);
          }

        return unsharpMaskData;
      }

    @CheckForNull
    public Document getXMLData()
      {
        return xmlData;
      }

    @CheckForNull
    public Document getHistoryStep()
      {
        return historyStep;
      }

    ////////////////////////////////////////////////////////////////////////////////
    @Override
    @Nonnull
    public String toString()
      {
        final StringBuilder buffer = new StringBuilder("CaptureEditorMetadata[");
        buffer.append("\n>>>>Orientation: " + getOrientation() + " degrees, ");
        buffer.append("\n>>>>crop: " + cropObject);

        if (isAdvancedRawEnabled())
          {
            buffer.append("\n>>>>EV compensation: " + (getEVCompensation() / 100.0) + ", ");
            buffer.append("Sharpening: " + getConstant(sharpeningMap, getSharpening()) + ", ");
            buffer.append("Tone comp: " + getConstant(toneCompMap, getToneCompensation()) + ", ");
            buffer.append("Color mode: " + getConstant(colorModeMap, getColorMode()) + ", ");

            if (isHueCorrectionEnabled())
              {
                buffer.append("Hue correction: " + getConstant(hueMap, getHueCorrection()) + ", ");
              }

            if (isSaturationCompensationEnabled())
              {
                buffer.append("Saturation compensation: " + getSaturationCompensation() + ", ");
              }
          }

        if (isWhiteBalanceEnabled())
          {
            buffer.append("\n>>>>Red: " + getWhiteBalanceRedCoeff() + ", ");
            buffer.append("Blue: " + getWhiteBalanceBlueCoeff() + ", ");
            buffer.append("White point: " + getWhitePointAsString(getWhiteBalanceWhitePoint()) + ", ");
            buffer.append("White point fine: " + getWhiteBalanceWhitePointFine() + ", ");
            buffer.append("Temperature: " + getWhiteBalanceTemperature());
          }

        if (isColorBoosterEnabled())
          {
            buffer.append("\n>>>>ColorBooster type: " + getConstant(colorBoosterMap, getColorBoosterType()) + ", ");
            buffer.append("ColorBooster value: " + getColorBoosterLevel());
          }

        if (isNoiseReductionEnabled())
          {
            buffer.append("\n>>>>Noise reduction: " + getNoiseReduction() + ", ");
            buffer.append("Edge reduction: " + isEdgeNoiseReductionEnabled() + ", ");
            buffer.append("Moire reduction: " + getConstant(moireReductionMap, getMoireReduction()));
          }

        if (isPhotoEffectEnabled())
          {
            buffer.append("\n>>>>Effect: " + getConstant(photoEffectMap, getPhotoEffect()) + ", ");
            buffer.append("Cyan/Red balance: " + getCyanRedBalance() + ", ");
            buffer.append("Magenta/Green balance: " + getMagentaGreenBalance() + ", ");
            buffer.append("Yellow/Bluebalance: " + getYellowBlueBalance());
          }

        if (isUnsharpMaskEnabled())
          {
            UnsharpMaskData[] unsharpMaskData = getUnsharpMaskData();

            for (int i = 0; i < unsharpMaskData.length; i++)
              {
                buffer.append("\n>>>>Unsharp mask[" + i + "]: ");
                buffer.append("type: " + getConstant(unsharpMaskMap, unsharpMaskData[i].getType()) + ", ");
                buffer.append("intensity: " + unsharpMaskData[i].getIntensity() + "%, ");
                buffer.append("halo width: " + unsharpMaskData[i].getHaloWidth() + "%, ");
                buffer.append("threshold: " + unsharpMaskData[i].getThreshold());
              }
          }

        buffer.append("\n]");

        return buffer.toString();
      }

    /**
     * @return
     */
    @CheckForNull
    public String getWhitePointAsString (final int whitePoint)
      {
        return getConstant(white1Map, whitePoint);
      }

    @CheckForNull
    public String getWhitePointFineAsString (final int whitePoint)
      {
        return getConstant(white2Map, whitePoint);
      }

    private static void registerConstant (final @Nonnull Map map,
                                          final int key,
                                          final @Nonnull String text)
      {
        map.put(key, text);
      }

    @CheckForNull
    private static String getConstant (final @Nonnull Map map,
                                       final int key)
      {
        String string = (String)map.get(key);

        return (string != null) ? string : ("#" + key + " - 0x" + Integer.toHexString(key));
      }

    @CheckForNull
    public static String getColorModeAsString (final int colorMode)
      {
        return getConstant(colorModeMap, colorMode);
      }

    @CheckForNull
    public static String getSharpeningAsString (final int sharpening)
      {
        return getConstant(sharpeningMap, sharpening);
      }

    @CheckForNull
    public static String getToneCompensationAsString (final int toneComp)
      {
        return getConstant(toneCompMap, toneComp);
      }

    @CheckForNull
    public static String getUnsharpMaskTypeAsString (final int type)
      {
        return getConstant(unsharpMaskMap, type);
      }

    /**
     * @param offset
     * @param id
     * @param size
     * @param buffer
     * @param i
     * @return
     */
    @Nonnull
    private String getDebugString (final @Nonnegative int offset,
                                   final int id,
                                   final @Nonnegative int size,
                                   final @Nonnull ByteBuffer subBuffer)
      {
        String s;
        final StringBuilder bbb = new StringBuilder();
        bbb.append("subBuffer ");
        bbb.append("0x" + Integer.toHexString(id));
        bbb.append(" at ");
        bbb.append(Integer.toString(offset));
        bbb.append(", size: ");
        bbb.append(size);
        bbb.append(", data: ");

        for (int j = 0; j < size; j++)
          {
            int b = subBuffer.get(j) & 0xFF;

            if (b < 0x10)
              {
                bbb.append("0");
              }

            bbb.append(Integer.toHexString(b));

            if (j > 64)
              {
                bbb.append("...");

                break;
              }

            bbb.append(" ");
          }

        s = bbb.toString();

        return s;
      }

    @Nonnull
    private String toString (final @Nonnull ByteBuffer buffer)
      {
        final StringBuilder builder = new StringBuilder();
        buffer.position(0);

        while (buffer.hasRemaining())
          {
            builder.append((char)buffer.get());
          }

        return builder.toString();
      }
  }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy