2012-01-23 4 views
10

imageIO를 사용하면 일반적으로 이미지 파일을 변환하는 데 문제가 있으며 이미지 파일을 덮어 쓰면 EXIF ​​데이터가 모두 손실됩니다. 먼저 압축을 풀고 캐싱 한 다음 다시 설정하지 않고 보존하는 방법이 있습니까?EXIF ​​데이터를 삭제하지 않고 이미지 조작

+2

다른 위치에 저장 한 다음 이전 exif 메타로 새 이미지를 덮어 씁니까? http://www.screaming-penguin.com/node/7485 –

+0

이것은 정확히 내가 피하고 싶습니다 – preslavrachev

+2

메타를 복사 할 때의 문제점은 무엇입니까? http://nucleussystems.com/blog/java-copy-exif-data –

답변

9

여기 ImageIO, Imgscalr 및 Apache commons-imaging의 조합을 사용하는 나의 해결책이 있습니다. 거기에는 유감스럽게도 이미지를 메타 데이터와 함께 읽는 단일 라이브러리가 없기 때문에 메모리 사용량이 과도하게 많습니다. 개선 된 점을 환영합니다.

ImageReader reader = ImageIO.getImageReadersBySuffix("jpg").next(); 

(당신은 그런 독자가 존재하는 경우도 확인 할 수 있습니다) :

import java.awt.image.BufferedImage; 
import java.io.ByteArrayInputStream; 
import java.io.ByteArrayOutputStream; 
import java.io.IOException; 
import java.io.InputStream; 
import java.io.OutputStream; 
import javax.imageio.ImageIO; 
import org.apache.commons.imaging.ImageReadException; 
import org.apache.commons.imaging.ImageWriteException; 
import org.apache.commons.imaging.Imaging; 
import org.apache.commons.imaging.common.IImageMetadata; 
import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata; 
import org.apache.commons.imaging.formats.jpeg.exif.ExifRewriter; 
import org.apache.commons.imaging.formats.tiff.TiffImageMetadata; 
import org.apache.commons.io.IOUtils; 
import org.imgscalr.Scalr; 


public class ImageData { 

    private byte[] imageData; 


    public ImageData(InputStream instream) throws IOException { 
     imageData = IOUtils.toByteArray(instream); 
     instream.close(); 
    } 


    public synchronized void resize(int maxDimension) throws IOException, ImageReadException, ImageWriteException { 
     // Resize the image if necessary 
     BufferedImage image = readImage(imageData); 
     if (image.getWidth() > maxDimension || image.getHeight() > maxDimension) { 

      // Save existing metadata, if any 
      TiffImageMetadata metadata = readExifMetadata(imageData); 
      imageData = null; // allow immediate GC 

      // resize 
      image = Scalr.resize(image, maxDimension); 

      // rewrite resized image as byte[] 
      byte[] resizedData = writeJPEG(image); 
      image = null; // allow immediate GC 

      // Re-code resizedData + metadata to imageData 
      if (metadata != null) { 
       this.imageData = writeExifMetadata(metadata, resizedData); 
      } else { 
       this.imageData = resizedData; 
      } 
     } 
    } 

    private TiffImageMetadata readExifMetadata(byte[] jpegData) throws ImageReadException, IOException { 
     IImageMetadata imageMetadata = Imaging.getMetadata(jpegData); 
     if (imageMetadata == null) { 
      return null; 
     } 
     JpegImageMetadata jpegMetadata = (JpegImageMetadata)imageMetadata; 
     TiffImageMetadata exif = jpegMetadata.getExif(); 
     if (exif == null) { 
      return null; 
     } 
     return exif; 
    } 


    private byte[] writeExifMetadata(TiffImageMetadata metadata, byte[] jpegData) 
           throws ImageReadException, ImageWriteException, IOException { 
     ByteArrayOutputStream out = new ByteArrayOutputStream(); 
     new ExifRewriter().updateExifMetadataLossless(jpegData, out, metadata.getOutputSet()); 
     out.close(); 
     return out.toByteArray(); 
    } 


    private BufferedImage readImage(byte[] data) throws IOException { 
     return ImageIO.read(new ByteArrayInputStream(data)); 
    } 

    private byte[] writeJPEG(BufferedImage image) throws IOException { 
     ByteArrayOutputStream jpegOut = new ByteArrayOutputStream(); 
     ImageIO.write(image, "JPEG", jpegOut); 
     jpegOut.close(); 
     return jpegOut.toByteArray(); 
    } 

    public synchronized void writeJPEG(OutputStream outstream) throws IOException { 
     IOUtils.write(imageData, outstream); 

    } 

    public synchronized byte[] getJPEGData() { 
     return imageData; 
    } 

} 
+0

고마워. 그것은 잘 작동했습니다. 유일한 것은 아파치 코 몬즈 이미징을위한 현재의 Repo에서'IImageMetadata'라는 이름이'ImageMetadata'입니다. –

+0

좀 더 효율적으로 보이는 @Rigeborod의 다른 솔루션도 확인하십시오 –

8

ImageIO에서이 기능 자체를 할 수 있지만, 대신 ImageIO.read 당신이하는 ImageReader를 사용해야합니다 . 당신이 당신의 메타 데이터를 저장할 수 있습니다 이제

reader.setInput(ImageIO.createImageInputStream(your_imput_stream)); 

:

IIOMetadata metadata = reader.getImageMetadata(0); 
          // As far as I understand you should provide 
          // index as tiff images could have multiple pages 

를 그리고 이미지를 읽어 새로운 이미지를 저장할

BufferedImage bi = reader.read(0); 

을 는 그런 다음 입력을 설정해야 ImageWriter를 사용해야합니다.

// I'm writing to byte array in memory, but you may use any other stream 
ByteArrayOutputStream os = new ByteArrayOutputStream(255); 
ImageOutputStream ios = ImageIO.createImageOutputStream(os); 

Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName("jpeg"); 
ImageWriter writer = iter.next(); 
writer.setOutput(ios); 

//You may want also to alter jpeg quality 
ImageWriteParam iwParam = writer.getDefaultWriteParam(); 
iwParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); 
iwParam.setCompressionQuality(.95f); 

//Note: we're using metadata we've already saved. 
writer.write(null, new IIOImage(bi, null, metadata), iwParam); 
writer.dispose(); 

ImageIO.write(bi, "jpg", ios); 

옛날 주제이기 때문에이 답변은 조금 늦은 것 같지만이 주제가 여전히 귀엽기 때문에 다른 사람들에게 도움이 될 수 있습니다.

+0

이것은 내 솔루션보다 훨씬 메모리 효율적인 것으로 보입니다. 나는 이미지의 한 복사본이 여전히 메모리에 있다고 생각하지만 실제로 그것을 피할 수는 없다. –

관련 문제