1

I'm currently making a method that converts a ppm file to a jpg, png, and bmp file. The way I did it is reading the content of a ppm file, creating a BufferedImage, and assigning each pixel from the ppm file to the corresponding pixel in the BufferedImage. My bmp and png files look correct. However, the jpg file looks completely different.

Below is my code:

import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

import javax.imageio.ImageIO;

public class readPPMOutputOthers {

  public static void main(String[] args) throws InterruptedException {
    // read a ppm file

    Scanner sc;
    // if the file is not found, it will throw an exception
    try {
      sc = new Scanner(new FileInputStream("res/test2.ppm"));
    } catch (FileNotFoundException e) {
      throw new IllegalArgumentException("File not found!");
    }

    // the file now is a StringBuilder
    // read line by line to get information
    StringBuilder builder = new StringBuilder();
    while (sc.hasNextLine()) {
      String s = sc.nextLine();
      // ignore comment #
      if (s.charAt(0) != '#') {
        builder.append(s).append(System.lineSeparator());
      }
    }

   
    sc = new Scanner(builder.toString());
    String token;
    token = sc.next();

    // set the fields
    // initial load image
    int width = sc.nextInt();
    int height = sc.nextInt();
    int maxValue = sc.nextInt();

    List<Integer> pixels = new ArrayList<>();
    for (int i = 0; i < height; i++) {
      for (int j = 0; j < width; j++) {
        int r = sc.nextInt();
        int g = sc.nextInt();
        int b = sc.nextInt();

        int rgb = r;
        rgb = (rgb << 8) + g;
        rgb = (rgb << 8) + b;
        pixels.add(rgb);
      }
    }

    // make a BufferedImage from pixels
    BufferedImage outputImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    int[] outputImagePixelData = ((DataBufferInt) outputImg.getRaster().getDataBuffer()).getData();

    for (int i = 0; i < pixels.size(); i++) {
      outputImagePixelData[i] = pixels.get(i);
    }

    try {
        ImageIO.write(outputImg, "png",
            new File("res/test.png"));
      ImageIO.write(outputImg, "jpg",
          new File("res/test2.jpg"));
        ImageIO.write(outputImg, "bmp",
            new File("res/test.bmp"));
    } catch (IOException e) {
      System.out.println("Exception occurred :" + e.getMessage());
    }
    System.out.println("Images were written successfully.");
  }
}

images comparison

The weird thing is it works for a very large image but not for this small image. I need to make it work for such small images because of testing. I've been digging posts about this on google and still didn't find a way to solve this. Any help would be appreciated!

d0nut
  • 29
  • 1
  • 5
  • What is an example that doesn't work? – DuncG Jun 11 '22 at 07:46
  • https://i.stack.imgur.com/GTrpa.png I'm not sure if this link works for you – d0nut Jun 11 '22 at 14:21
  • That links to a png not `res/test2.ppm` as used as input by your example code. – DuncG Jun 11 '22 at 17:42
  • it seems like I can't upload a ppm file here. but here is the content of the file: "P3 2 2 255 255 0 0 0 255 255 255 0 255 255 255 0 " – d0nut Jun 11 '22 at 18:54
  • @d0nut Are you getting correct colors when saving the image as PNG? – Rotem Jun 11 '22 at 18:56
  • @Rotem yes, just jpg/jpeg that is not working – d0nut Jun 11 '22 at 18:56
  • 1
    @d0nut I figured out the reason. JPEG chroma sub-sumpling default is [YUV420](https://en.wikipedia.org/wiki/Chroma_subsampling). In YUV420 every 2x2 pixels have the same chroma information (the 2x2 pixels have the same color). The 2x2 pixels have the same color but each pixel has different luminance. That is the reason the 4 pixels has the same color (the color is a mixture of the 4 original colors). – Rotem Jun 11 '22 at 19:37
  • There is a way to save JPEG image in YUV444 chroma sub-sumpling, but it's much less common. I found [this post](https://stackoverflow.com/questions/14149739/disable-java-imageio-chroma-subsampling) for example, but I can't find good answers... GIMP manages to save JPEG with YUV444 chroma sub-sumpling, so there is way for doing it. – Rotem Jun 11 '22 at 19:42
  • @Rotem I think you are correct, and it would be great if you could rework your comment into an answer. Theoretically, JPEG can be stored as plain RGB or non-subsampled YCbCr (YUV) in addition to the more common subsampled YCbCr, but there's no easy way to control subsampling or output "color space" through the ImageIO JPEG plugin, unfortunately. – Harald K Jun 13 '22 at 12:30
  • @HaraldK do you know any other library that can help me output a jpg file correctly? – d0nut Jun 13 '22 at 20:16
  • It’s not really about the library. It’s the technology. Even if you find a library that outputs the image like you want, it will be larger than the equivalent BMP, PNG and GIF. JPEG is great for compressing “natural” images. Not so for “artificial” images like this with solid colors, sharp edges and in your case extremely small size. For more “normal” images, the standard ImageIO JPEG plugin will do just fine. – Harald K Jun 14 '22 at 07:13
  • The answer in [this post](https://stackoverflow.com/a/14150769/1428606) seems to work for me, writing a JPEG with 4:4:4 (no chroma subsampling). The result has visually correct colors (close to the PNG/BMP). The JPEG is 467 bytes, compared to the 70 byte BMP and 76 byte PNG. – Harald K Jun 14 '22 at 08:20

2 Answers2

3

The reason for the strange colors is YUV420 chroma subsumpling used by JPEG encoding.

In YUV420 every 2x2 pixels have the same chroma information (the 2x2 pixels have the same color).
The 2x2 pixels have the same color, but each pixel has different luminance (brighness).


The YUV420 Chroma subsumpling is demonstrated in Wikipedia:
enter image description here

And in our case:
enter image description here becomes enter image description here
The brown color is a mixture of the original red, cyan magenta and the yellow colors (the brown color is "shared" by the 4 pixels).


  • Note:
    Chroma subsumpling is not considered as "compression", is the sense that it not performed as part of the JPEG compression stage.
    We can't control the chroma subsumpling by setting the compression quality parameter.
    Chroma subsumpling is referred as part of the "color format conversion" pre-processing stage - converting from RGB to YUV420 color format.

The commonly used JPEG color format is YUV420, but JPEG standard does support YUV444 Chroma subsumpling.
GIMP manages to save JPEG images with YUV444 Chroma subsumpling.

Example (2x2 image):
Too small: enter image description here Enlarged: enter image description here

I couldn't find an example for saving YUV444 JPEG in JAVA...

Rotem
  • 30,366
  • 4
  • 32
  • 65
0

To some degree the effect you describe is to be expected.

From https://en.wikipedia.org/wiki/JPEG:

JPEG is a commonly used method of lossy compression for digital images, particularly for those images produced by digital photography. The degree of compression can be adjusted, allowing a selectable tradeoff between storage size and image quality. JPEG typically achieves 10:1 compression with little perceptible loss in image quality.

Maybe when storing small files you can set the compression to be low and thus increase quality. See Setting jpg compression level with ImageIO in Java

Queeg
  • 7,748
  • 1
  • 16
  • 42
  • I tried every compression from 0.0f to 1.0f, but the colors are all different from the original image. – d0nut Jun 11 '22 at 15:11
  • Is that happening on a 2x2 pixels image? Wondering whether the compression algorithms may work on a 4 or 8 pixels compression algorighm which has bugs at that small scale... – Queeg Jun 11 '22 at 19:29
  • @HiranChaudhuri I found that the reason is the YUV420 chroma sub-sumpling, where every 2x2 pixels have the same chroma information (same color). I am going to up vote your answer if you post a solution that saves a JPEG image in YUV444 chroma sub-sumpling. – Rotem Jun 11 '22 at 19:45