Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TIFF Photometric Interpretation Set to 1 (one) #533

Closed
aabrami opened this issue Mar 11, 2020 · 4 comments
Closed

TIFF Photometric Interpretation Set to 1 (one) #533

aabrami opened this issue Mar 11, 2020 · 4 comments

Comments

@aabrami
Copy link

aabrami commented Mar 11, 2020

I am using Java 8 (32 bit) on Windows 7 (32 bit) with twelvemonkeys version 3.4
I read two files where each file contains a single TIFF image and create two, separate BufferedImages.
I create a new BufferedImage that contains the two images.
Then I save that image to a file.
The photometric interpretation value for the new image is 1 (one) whereas it is 0 (zero) for both the source images. I want the photometric interpretation value to be 0 (zero) for the image I create. Note that my requirements for the created image are 200 DPI and CCITT T.6 compression.
Here is sample code.

FileOutputStream fos = null;
Graphics2D graphics = null;
ImageWriter writer = null;
try {
    BufferedImage first = ImageIO.read(new File(args[0]));
    BufferedImage secnd = ImageIO.read(new File(args[1]));
    int heightFirst = first.getHeight();
    int heightSecnd = secnd.getHeight();
    int totalHeight = heightFirst + heightSecnd;
    int widthFirst = first.getWidth();
    int widthSecnd = secnd.getWidth();
    int width = Math.max(widthFirst, widthSecnd);
    BufferedImage both = new BufferedImage(width, totalHeight, BufferedImage.TYPE_BYTE_BINARY);
    graphics = both.createGraphics();
    graphics.drawImage(first, 0, 0, null);
    graphics.drawImage(secnd, 0, heightFirst, null);
    Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("tiff");
    writer = writers.next();  //throws java.util.NoSuchElementException
    ImageWriteParam iwp = writer.getDefaultWriteParam();
    Collection<Entry> entries = new ArrayList<Entry>();
    Entry xResolution = new TIFFEntry(TIFF.TAG_X_RESOLUTION, new Rational(200L));
    Entry yResolution = new TIFFEntry(TIFF.TAG_Y_RESOLUTION, new Rational(200L));
    entries.add(xResolution);
    entries.add(yResolution);
    IIOMetadata imgMetaData = new TIFFImageMetadata(entries);
    iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
    iwp.setCompressionType("CCITT T.6");
    fos = new FileOutputStream("C:\\temp\\Logs\\testimgs.tif");
    ImageOutputStream ios = ImageIO.createImageOutputStream(fos);
    writer.setOutput(ios);
    writer.write(null, new IIOImage(both, null, imgMetaData), iwp);
}
catch (Exception x) {
    x.printStackTrace();
}
finally {
    if (fos != null) {
        try {
            fos.close();
        }
        catch (IOException xIo) {
            // Ignore.
        }
    }
    if (graphics != null) {
        graphics.dispose();
    }
    if (writer != null) {
        writer.dispose();
    }
}

Security at my place of work is not allowing me to upload the actual TIFF images.
Both source images are binary, i.e. BufferedImage.TYPE_BYTE_BINARY and both have identical IndexColorModel.

@haraldk
Copy link
Owner

haraldk commented Mar 11, 2020

I believe this is exactly the same issue as #478 .

You can't (currently) use the default constructor new BufferedImage(w, h, BufferedImage.TYPE_BYTE_BINARY);, as the color model in that has black in index 0 (ie. -> blackIsZero). While we might change that in the future, so that we write FAX encodings as whiteIsZero by default, for now you have to make sure the color model is correct.

Use the constructor that takes a two color IndexColorModel as a fourth argument, and make sure you use a color model that has white in index 0, something like:

new IndexColorModel(1, 2, new int[] {-1, 0}, 0, false, -1, DataBuffer.TYPE_BYTE);

Let me know if that doesn't fix your problem!

Best regards,

--
Harald K

@aabrami
Copy link
Author

aabrami commented Mar 12, 2020

Thank you very much for your prompt reply. Here is my code for creating the new image.

IndexColorModel icm = new IndexColorModel(1,
                                          2,
                                          new int[]{-1, 0},
                                          0,
                                          false,
                                          Transparency.OPAQUE,
                                          DataBuffer.TYPE_BYTE);
BufferedImage both = new BufferedImage(width,
                                       totalHeight,
                                       BufferedImage.TYPE_BYTE_BINARY,
                                       icm);

The value for photometric interpretation for the created image is 3 (three). Here is an excerpt of the meta-data for the created image.

<TIFFField number="262" name="PhotometricInterpretation">
  <TIFFShorts>
    <TIFFShort value="3"/>
  </TIFFShorts>
</TIFFField>

The problem is that the person I send the created image to is using a viewer that respects this value so he sees the image "reversed", i.e. black is white and white is black. Of-course other viewers, like Windows Photo Viewer, display the image correctly. I don't have the same viewer as my client so I can't check. Will the value 3 (for photometric interpretation) yield the same result as 0 (zero)?

I asked the client to supply details of his viewer but so far he has not. All I can tell is that it is called ADA System Viewer.

@haraldk
Copy link
Owner

haraldk commented Mar 12, 2020

Sorry, my mistake. I just wrote the above code from the top of my head without testing. I fixed the code sample above.

You need to specify the trans, the transparent pixel parameter, as -1 (not Transparency.OPAQUE as I originally wrote. I often make this mistake as Transparency.OPAQUE unfortunately is an int with the value 1, which is perfectly legal, but turns the color at index 1 into transparent, which is absolutely not what we want... Bad API I guess).

And no, Photometric 3 is Palette, and will not work the same as 0 (WhiteIsZero). The writer will write the value 3/Palette, if the colors in the color map are different than opaque black/white. But with the fixed code above, you should be good. This time I tested. 😀

Here's a full unit test as proof:

@Test
public void testWriteWhiteIsZero() throws IOException {
    IndexColorModel icm = new IndexColorModel(1, 2, new int[] {-1, 0}, 0, false, -1, DataBuffer.TYPE_BYTE);
    BufferedImage image = new BufferedImage(100, 100, BufferedImage.TYPE_BYTE_BINARY, icm);

    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    try (ImageOutputStream output = ImageIO.createImageOutputStream(bytes)) {
        ImageWriter imageWriter = createImageWriter();
        imageWriter.setOutput(output);
        imageWriter.write(image);
    }

    Directory directory = new TIFFReader().read(new ByteArrayImageInputStream(bytes.toByteArray()));

    assertNotNull(directory.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION));
    assertEquals(TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO, directory.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION).getValue());
}

Best regards,

--
Harald K

@haraldk haraldk closed this as completed Mar 12, 2020
@aabrami
Copy link
Author

aabrami commented Mar 12, 2020 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants