IMAGING-186: Add sCAL support to PNG reading and writing. Thanks to Ric Emery. This also closes #23 from GitHub.

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/imaging/trunk@1754653 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 14f224a..4730adb 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -46,6 +46,9 @@
   <body>
 
     <release version="1.0" date="TBA" description="First major release">
+      <action issue="IMAGING-186" dev="britter" type="update" due-to="Ric Emery">
+        Add sCAL support to PNG reading and writing.
+      </action>
       <action issue="IMAGING-176" dev="britter" type="fix" due-to="Gabriel Axel">
         TiffImageParser.getImageInfo() throws exception when "Compression" field is missing.
       </action>
diff --git a/src/main/java/org/apache/commons/imaging/formats/png/ChunkType.java b/src/main/java/org/apache/commons/imaging/formats/png/ChunkType.java
index 3231635..0035bb7 100644
--- a/src/main/java/org/apache/commons/imaging/formats/png/ChunkType.java
+++ b/src/main/java/org/apache/commons/imaging/formats/png/ChunkType.java
@@ -75,6 +75,9 @@
     /** Physical pixel dimensions */
     pHYs,
 
+    /** Physical scale */
+    sCAL,
+
     /** Suggested palette */
     sPLT,
 
diff --git a/src/main/java/org/apache/commons/imaging/formats/png/PhysicalScale.java b/src/main/java/org/apache/commons/imaging/formats/png/PhysicalScale.java
new file mode 100644
index 0000000..ca1d657
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/formats/png/PhysicalScale.java
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+package org.apache.commons.imaging.formats.png;
+
+/**
+ * Used to specify physical scale when reading or storing image information.
+ */
+public class PhysicalScale {
+   private static final int METER_UNITS = 1;
+   private static final int RADIAN_UNITS = 2;
+   public static final PhysicalScale UNDEFINED = createFromMeters(-1.0, -1.0);
+
+   private final int units;
+   private final double horizontalUnitsPerPixel;
+   private final double verticalUnitsPerPixel;
+
+   private PhysicalScale(final int units, final double horizontalUnitsPerPixel,
+                         final double verticalUnitsPerPixel) {
+      this.units = units;
+      this.horizontalUnitsPerPixel = horizontalUnitsPerPixel;
+      this.verticalUnitsPerPixel = verticalUnitsPerPixel;
+   }
+
+   public static PhysicalScale createFromMeters(final double x, final double y) {
+      return new PhysicalScale(METER_UNITS, x, y);
+   }
+
+   public static PhysicalScale createFromRadians(final double x, final double y) {
+      return new PhysicalScale(RADIAN_UNITS, x, y);
+   }
+
+   public boolean isInMeters() {
+      return METER_UNITS == units;
+   }
+
+   public boolean isInRadians() {
+      return RADIAN_UNITS == units;
+   }
+
+   public double getHorizontalUnitsPerPixel() {
+      return horizontalUnitsPerPixel;
+   }
+
+   public double getVerticalUnitsPerPixel() {
+      return verticalUnitsPerPixel;
+   }
+}
diff --git a/src/main/java/org/apache/commons/imaging/formats/png/PngConstants.java b/src/main/java/org/apache/commons/imaging/formats/png/PngConstants.java
index c712d11..a17e1b3 100644
--- a/src/main/java/org/apache/commons/imaging/formats/png/PngConstants.java
+++ b/src/main/java/org/apache/commons/imaging/formats/png/PngConstants.java
@@ -69,6 +69,16 @@
      */
     public static final String PARAM_KEY_PNG_TEXT_CHUNKS = "PNG_TEXT_CHUNKS";
 
+    /**
+     * Parameter key. Used in write operations to indicate the Physical Scale - sCAL.
+     * <p>
+     * Valid values: PhysicalScale
+     * <p>
+     *
+     * @see org.apache.commons.imaging.formats.png.PhysicalScale
+     */
+    public static final String PARAM_KEY_PHYSICAL_SCALE = "PHYSICAL_SCALE_CHUNK";
+
     private PngConstants() {
     }
 }
diff --git a/src/main/java/org/apache/commons/imaging/formats/png/PngImageInfo.java b/src/main/java/org/apache/commons/imaging/formats/png/PngImageInfo.java
index 74b7657..7e6e254 100644
--- a/src/main/java/org/apache/commons/imaging/formats/png/PngImageInfo.java
+++ b/src/main/java/org/apache/commons/imaging/formats/png/PngImageInfo.java
@@ -24,6 +24,7 @@
 
 public class PngImageInfo extends ImageInfo {
     private final List<PngText> textChunks;
+    private final PhysicalScale physicalScale;
 
     PngImageInfo(final String formatDetails, final int bitsPerPixel,
             final List<String> comments, final ImageFormat format, final String formatName,
@@ -32,7 +33,7 @@
             final int physicalWidthDpi, final float physicalWidthInch, final int width,
             final boolean progressive, final boolean transparent, final boolean usesPalette,
             final ColorType colorType, final CompressionAlgorithm compressionAlgorithm,
-            final List<PngText> textChunks) {
+            final List<PngText> textChunks, final PhysicalScale physicalScale) {
         super(formatDetails, bitsPerPixel, comments, format, formatName,
                 height, mimeType, numberOfImages, physicalHeightDpi,
                 physicalHeightInch, physicalWidthDpi, physicalWidthInch, width,
@@ -40,10 +41,20 @@
                 compressionAlgorithm);
 
         this.textChunks = textChunks;
+        this.physicalScale = physicalScale;
     }
 
     public List<PngText> getTextChunks() {
         return new ArrayList<>(textChunks);
     }
 
+   /**
+    * Physical scale of Image.
+    *
+    * @return {@link PhysicalScale}. If undefined then {@link PhysicalScale#UNDEFINED}
+    * is returned.
+    */
+    public PhysicalScale getPhysicalScale() {
+        return physicalScale;
+    }
 }
diff --git a/src/main/java/org/apache/commons/imaging/formats/png/PngImageParser.java b/src/main/java/org/apache/commons/imaging/formats/png/PngImageParser.java
index b22bc3e..b2912f8 100644
--- a/src/main/java/org/apache/commons/imaging/formats/png/PngImageParser.java
+++ b/src/main/java/org/apache/commons/imaging/formats/png/PngImageParser.java
@@ -52,6 +52,7 @@
 import org.apache.commons.imaging.formats.png.chunks.PngChunkItxt;
 import org.apache.commons.imaging.formats.png.chunks.PngChunkPhys;
 import org.apache.commons.imaging.formats.png.chunks.PngChunkPlte;
+import org.apache.commons.imaging.formats.png.chunks.PngChunkScal;
 import org.apache.commons.imaging.formats.png.chunks.PngChunkText;
 import org.apache.commons.imaging.formats.png.chunks.PngChunkZtxt;
 import org.apache.commons.imaging.formats.png.chunks.PngTextChunk;
@@ -191,6 +192,8 @@
                     result.add(new PngChunkPlte(length, chunkType, crc, bytes));
                 } else if (chunkType == ChunkType.pHYs.value) {
                     result.add(new PngChunkPhys(length, chunkType, crc, bytes));
+                } else if (chunkType == ChunkType.sCAL.value) {
+                    result.add(new PngChunkScal(length, chunkType, crc, bytes));
                 } else if (chunkType == ChunkType.IDAT.value) {
                     result.add(new PngChunkIdat(length, chunkType, crc, bytes));
                 } else if (chunkType == ChunkType.gAMA.value) {
@@ -338,6 +341,7 @@
         final List<PngChunk> chunks = readChunks(byteSource, new ChunkType[] {
                 ChunkType.IHDR,
                 ChunkType.pHYs,
+                ChunkType.sCAL,
                 ChunkType.tEXt,
                 ChunkType.zTXt,
                 ChunkType.tRNS,
@@ -380,6 +384,23 @@
             pngChunkpHYs = (PngChunkPhys) pHYss.get(0);
         }
 
+        PhysicalScale physicalScale = PhysicalScale.UNDEFINED;
+
+        final List<PngChunk> sCALs = filterChunks(chunks, ChunkType.sCAL);
+        if (sCALs.size() > 1) {
+            throw new ImageReadException("PNG contains more than one sCAL:"
+                    + sCALs.size());
+        } else if (sCALs.size() == 1) {
+            PngChunkScal pngChunkScal = (PngChunkScal) sCALs.get(0);
+            if (pngChunkScal.unitSpecifier == 1) {
+                physicalScale = PhysicalScale.createFromMeters(pngChunkScal.unitsPerPixelXAxis,
+                      pngChunkScal.unitsPerPixelYAxis);
+            } else {
+                physicalScale = PhysicalScale.createFromRadians(pngChunkScal.unitsPerPixelXAxis,
+                      pngChunkScal.unitsPerPixelYAxis);
+            }
+        }
+
         final List<PngChunk> tEXts = filterChunks(chunks, ChunkType.tEXt);
         final List<PngChunk> zTXts = filterChunks(chunks, ChunkType.zTXt);
         final List<PngChunk> iTXts = filterChunks(chunks, ChunkType.iTXt);
@@ -465,7 +486,8 @@
                 format, formatName, height, mimeType, numberOfImages,
                 physicalHeightDpi, physicalHeightInch, physicalWidthDpi,
                 physicalWidthInch, width, progressive, transparent,
-                usesPalette, colorType, compressionAlgorithm, textChunks);
+                usesPalette, colorType, compressionAlgorithm, textChunks,
+                physicalScale);
     }
 
     @Override
diff --git a/src/main/java/org/apache/commons/imaging/formats/png/PngWriter.java b/src/main/java/org/apache/commons/imaging/formats/png/PngWriter.java
index b7c7812..73950e8 100644
--- a/src/main/java/org/apache/commons/imaging/formats/png/PngWriter.java
+++ b/src/main/java/org/apache/commons/imaging/formats/png/PngWriter.java
@@ -297,6 +297,22 @@
         writeChunk(os, ChunkType.pHYs, bytes);
     }
 
+    private void writeChunkSCAL(final OutputStream os, final double xUPP, final double yUPP, final byte units)
+          throws IOException {
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+        // unit specifier
+        baos.write(units);
+
+        // units per pixel, x-axis
+        baos.write(String.valueOf(xUPP).getBytes("ISO-8859-1"));
+        baos.write(0);
+
+        baos.write(String.valueOf(yUPP).getBytes("ISO-8859-1"));
+
+        writeChunk(os, ChunkType.sCAL, baos.toByteArray());
+    }
+
     private byte getBitDepth(final PngColorType pngColorType, final Map<String, Object> params) {
         byte depth = 8;
 
@@ -363,6 +379,7 @@
      hIST   No  After PLTE; before IDAT
      tRNS   No  After PLTE; before IDAT
      pHYs   No  Before IDAT
+     sCAL   No  Before IDAT
      sPLT   Yes Before IDAT
      tIME   No  None
      iTXt   Yes None
@@ -400,6 +417,7 @@
             params.remove(PngConstants.PARAM_KEY_PNG_TEXT_CHUNKS);
         }
         params.remove(ImagingConstants.PARAM_KEY_PIXEL_DENSITY);
+        params.remove(PngConstants.PARAM_KEY_PHYSICAL_SCALE);
         if (!params.isEmpty()) {
             final Object firstKey = params.keySet().iterator().next();
             throw new ImageWriteException("Unknown parameter: " + firstKey);
@@ -517,6 +535,13 @@
             }
         }
 
+        final Object physcialScaleObj = params.get(PngConstants.PARAM_KEY_PHYSICAL_SCALE);
+        if (physcialScaleObj instanceof PhysicalScale) {
+            final PhysicalScale physicalScale = (PhysicalScale)physcialScaleObj;
+            writeChunkSCAL(os, physicalScale.getHorizontalUnitsPerPixel(), physicalScale.getVerticalUnitsPerPixel(),
+                  physicalScale.isInMeters() ? (byte) 1 : (byte) 2);
+        }
+
         if (params.containsKey(ImagingConstants.PARAM_KEY_XMP_XML)) {
             final String xmpXml = (String) params.get(ImagingConstants.PARAM_KEY_XMP_XML);
             writeChunkXmpiTXt(os, xmpXml);
@@ -652,6 +677,7 @@
          hIST           No                  After PLTE; before IDAT
          tRNS           No                  After PLTE; before IDAT
          pHYs           No                  Before IDAT
+         sCAL           No                  Before IDAT
          sPLT           Yes                 Before IDAT
          tIME           No                  None
          iTXt           Yes                 None
diff --git a/src/main/java/org/apache/commons/imaging/formats/png/chunks/PngChunkScal.java b/src/main/java/org/apache/commons/imaging/formats/png/chunks/PngChunkScal.java
new file mode 100644
index 0000000..a22d4ae
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/formats/png/chunks/PngChunkScal.java
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+package org.apache.commons.imaging.formats.png.chunks;
+
+import org.apache.commons.imaging.ImageReadException;
+
+import java.io.IOException;
+
+import static org.apache.commons.imaging.common.BinaryFunctions.findNull;
+
+public class PngChunkScal extends PngChunk {
+   public final double unitsPerPixelXAxis;
+   public final double unitsPerPixelYAxis;
+   public final int unitSpecifier;
+
+   public PngChunkScal(int length, int chunkType, int crc, byte[] bytes)
+         throws ImageReadException, IOException {
+      super(length, chunkType, crc, bytes);
+
+      unitSpecifier = bytes[0];
+      if (unitSpecifier != 1 && unitSpecifier != 2) {
+         throw new ImageReadException("PNG sCAL invalid unit specifier: " + unitSpecifier);
+      }
+
+      final int separator = findNull(bytes);
+      if (separator < 0) {
+         throw new ImageReadException("PNG sCAL x and y axis value separator not found.");
+      }
+
+      final int xIndex = 1;
+      final String xStr = new String(bytes, xIndex, separator - 1, "ISO-8859-1");
+      unitsPerPixelXAxis = toDouble(xStr);
+
+      final int yIndex = separator + 1;
+      if (yIndex >= length) {
+         throw new ImageReadException("PNG sCAL chunk missing the y axis value.");
+      }
+
+      final String yStr = new String(bytes, yIndex, length - yIndex);
+      unitsPerPixelYAxis = toDouble(yStr);
+   }
+
+   private double toDouble(final String str) throws ImageReadException {
+      try {
+         return Double.valueOf(str);
+      } catch (NumberFormatException e) {
+         throw new ImageReadException("PNG sCAL error reading axis value - " + str);
+      }
+   }
+}
diff --git a/src/test/java/org/apache/commons/imaging/formats/png/PhysicalScaleTest.java b/src/test/java/org/apache/commons/imaging/formats/png/PhysicalScaleTest.java
new file mode 100644
index 0000000..e5cfffe
--- /dev/null
+++ b/src/test/java/org/apache/commons/imaging/formats/png/PhysicalScaleTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+package org.apache.commons.imaging.formats.png;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class PhysicalScaleTest {
+   private final double delta = 0.01;
+
+   @Test
+   public void createFromMeters() {
+      final PhysicalScale physicalScale = PhysicalScale.createFromMeters(1.0, 2.0);
+
+      assertTrue("Should be in meteres", physicalScale.isInMeters());
+      assertFalse("Should not be in radians", physicalScale.isInRadians());
+      assertEquals("Invalid horizontal units per pixel", physicalScale.getHorizontalUnitsPerPixel(), 1.0, delta);
+      assertEquals("Invalid vertical units per pixel", physicalScale.getVerticalUnitsPerPixel(), 2.0, delta);
+   }
+
+   @Test
+   public void createFromRadians() {
+      final PhysicalScale physicalScale = PhysicalScale.createFromRadians(2.0, 1.0);
+
+      assertFalse("Should not be in meteres", physicalScale.isInMeters());
+      assertTrue("Should be in radians", physicalScale.isInRadians());
+      assertEquals("Invalid horizontal units per pixel", physicalScale.getHorizontalUnitsPerPixel(), 2.0, delta);
+      assertEquals("Invalid vertical units per pixel", physicalScale.getVerticalUnitsPerPixel(), 1.0, delta);
+   }
+}
diff --git a/src/test/java/org/apache/commons/imaging/formats/png/PngWriteReadTest.java b/src/test/java/org/apache/commons/imaging/formats/png/PngWriteReadTest.java
index 1ceb7e3..b8ad2b6 100644
--- a/src/test/java/org/apache/commons/imaging/formats/png/PngWriteReadTest.java
+++ b/src/test/java/org/apache/commons/imaging/formats/png/PngWriteReadTest.java
@@ -18,6 +18,7 @@
 package org.apache.commons.imaging.formats.png;
 
 import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
@@ -107,6 +108,38 @@
         assertTrue(Imaging.getImageInfo(pngBytes).isTransparent());
     }
 
+    @Test
+    public void testPhysicalScaleMeters() throws Exception {
+        final Map<String, Object> optionalParams = new HashMap<>();
+        optionalParams.put(PngConstants.PARAM_KEY_PHYSICAL_SCALE, PhysicalScale.createFromMeters(0.01, 0.02));
+
+        final int[][] smallAscendingPixels = getAscendingRawData(256, 256);
+        final byte[] pngBytes = Imaging.writeImageToBytes(
+              imageDataToBufferedImage(smallAscendingPixels),
+              ImageFormats.PNG, optionalParams);
+        final PngImageInfo imageInfo = (PngImageInfo) Imaging.getImageInfo(pngBytes);
+        final PhysicalScale physicalScale = imageInfo.getPhysicalScale();
+        assertTrue("Invalid units", physicalScale.isInMeters());
+        assertEquals("Invalid horizontal units", 0.01, physicalScale.getHorizontalUnitsPerPixel(), 0.001);
+        assertEquals("Invalid vertical units", 0.02, physicalScale.getVerticalUnitsPerPixel(), 0.001);
+    }
+
+    @Test
+    public void testPhysicalScaleRadians() throws Exception {
+        final Map<String, Object> optionalParams = new HashMap<>();
+        optionalParams.put(PngConstants.PARAM_KEY_PHYSICAL_SCALE, PhysicalScale.createFromRadians(0.01, 0.02));
+
+        final int[][] smallAscendingPixels = getAscendingRawData(256, 256);
+        final byte[] pngBytes = Imaging.writeImageToBytes(
+              imageDataToBufferedImage(smallAscendingPixels),
+              ImageFormats.PNG, optionalParams);
+        final PngImageInfo imageInfo = (PngImageInfo) Imaging.getImageInfo(pngBytes);
+        final PhysicalScale physicalScale = imageInfo.getPhysicalScale();
+        assertTrue("Invalid units", physicalScale.isInRadians());
+        assertEquals("Invalid horizontal units", 0.01, physicalScale.getHorizontalUnitsPerPixel(), 0.001);
+        assertEquals("Invalid vertical units", 0.02, physicalScale.getVerticalUnitsPerPixel(), 0.001);
+    }
+
     private BufferedImage imageDataToBufferedImage(final int[][] rawData) {
         final int width = rawData[0].length;
         final int height = rawData.length;
diff --git a/src/test/java/org/apache/commons/imaging/formats/png/chunks/PngChunkScalTest.java b/src/test/java/org/apache/commons/imaging/formats/png/chunks/PngChunkScalTest.java
new file mode 100644
index 0000000..5e735d5
--- /dev/null
+++ b/src/test/java/org/apache/commons/imaging/formats/png/chunks/PngChunkScalTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+package org.apache.commons.imaging.formats.png.chunks;
+
+import org.apache.commons.imaging.ImageReadException;
+import org.junit.Test;
+
+import java.io.IOException;
+
+import static org.junit.Assert.assertEquals;
+
+public class PngChunkScalTest {
+   private final double delta = 0.001;
+   private final int chunkType = 1933787468;
+
+   @Test
+   public void testConstructMeters() throws IOException, ImageReadException {
+      final PngChunkScal pngChunkScal = new PngChunkScal(10, chunkType, 0,
+            new byte[]{1, 48, 46, 48, 49, 0, 48, 46, 48, 50});
+
+      assertEquals("Invalid unit specifier", pngChunkScal.unitSpecifier, 1);
+      assertEquals("Invalid units per pixel x axis", pngChunkScal.unitsPerPixelXAxis, 0.01, delta);
+      assertEquals("Invalid units per pixel y axis", pngChunkScal.unitsPerPixelYAxis, 0.02, delta);
+   }
+
+   @Test
+   public void testConstructRadians() throws IOException, ImageReadException {
+      final PngChunkScal pngChunkScal = new PngChunkScal(10, chunkType, 0,
+            new byte[]{2, 48, 46, 48, 49, 0, 48, 46, 48, 50});
+
+      assertEquals("Invalid unit specifier", pngChunkScal.unitSpecifier, 2);
+      assertEquals("Invalid units per pixel x axis", pngChunkScal.unitsPerPixelXAxis, 0.01, delta);
+      assertEquals("Invalid units per pixel y axis", pngChunkScal.unitsPerPixelYAxis, 0.02, delta);
+   }
+
+   @Test(expected = ImageReadException.class)
+   public void testConstruct_InvalidUnitSpecifier() throws IOException, ImageReadException {
+      new PngChunkScal(10, chunkType, 0, new byte[]{3, 48, 46, 48, 49, 0, 48, 46, 48, 50});
+   }
+
+   @Test(expected = ImageReadException.class)
+   public void testConstruct_MissingSeparator() throws IOException, ImageReadException {
+      new PngChunkScal(9, chunkType, 0, new byte[]{1, 48, 46, 48, 49, 48, 46, 48, 50});
+   }
+
+   @Test(expected = ImageReadException.class)
+   public void testConstruct_InvalidDblValue() throws IOException, ImageReadException {
+      new PngChunkScal(10, chunkType, 0, new byte[]{2, 65, 46, 48, 49, 0, 48, 46, 48, 50});
+   }
+
+   @Test(expected = ImageReadException.class)
+   public void testConstruct_MissingXValue() throws IOException, ImageReadException {
+      new PngChunkScal(2, chunkType, 0, new byte[]{2, 0});
+   }
+
+   @Test(expected = ImageReadException.class)
+   public void testConstruct_MissingYValue() throws IOException, ImageReadException {
+      new PngChunkScal(6, chunkType, 0, new byte[]{2, 48, 46, 48, 49, 0});
+   }
+}