简体   繁体   中英

Why is the color of my image changed after writing it as a jpg file?

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!

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 :
在此处输入图像描述

And in our case:
在此处输入图像描述 becomes在此处输入图像描述
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: 在此处输入图像描述 Enlarged: 在此处输入图像描述

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM