2016-11-26 6 views
1

Я создаю программу, которая отображает анимированные gif. Поскольку некоторые анимированные gif-файлы сохраняют только пиксели, которые были изменены из предыдущего кадра, перед тем, как каждый кадр отображается, он тянется к объекту мастера BufferedImage с именем master, а затем рисуется BufferedImage. Проблема в том, что рисование кадров (хранимых как BufferedImage объектов) на master снижает их качество.Потеря качества изображения при рисовании одного BufferedImage другому с помощью Graphics2D

Я знаю, что это не проблема с самими кадрами, если я просто рисую рамки отдельно, не рисуя их до master, тогда они выглядят отлично. Это также не проблема с тем фактом, что много кадров накладываются друг на друга, даже первый кадр показывает снижение качества. Я пробовал устанавливать каждый RenderingHint на все возможные значения, но ничего не меняет.

Ниже мой код, с ненужными частями для решения этой проблемы опущена:

import java.awt.image.BufferedImage; 
import java.awt.Component; 
import java.awt.Dimension; 
import java.awt.Graphics; 
import java.awt.Graphics2D; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 
import java.io.File; 
import java.io.IOException; 
import java.util.ArrayList; 
import java.util.Iterator; 
import javax.activation.MimetypesFileTypeMap; 
import javax.imageio.IIOImage; 
import javax.imageio.ImageIO; 
import javax.imageio.ImageReader; 
import javax.imageio.metadata.IIOMetadata; 
import javax.imageio.metadata.IIOMetadataNode; 
import javax.imageio.stream.FileImageInputStream; 
import javax.swing.JFrame; 
import javax.swing.JPanel; 
import javax.swing.SwingUtilities; 
import javax.swing.Timer; 

@SuppressWarnings("serial") 
class A extends javax.swing.JPanel{ 

    public static final String PATH = "C:/Users/Owner/Desktop/test.gif"; 
    public B i; 


    public A() throws java.io.IOException{ 
     i = new B(new java.io.File(PATH)); 
     i.registerComponent(this); 
    } 

    @Override 
    public java.awt.Dimension preferredSize(){ 
     return i.getSize(); 
    } 

    @Override 
    public void paintComponent(java.awt.Graphics g){ 
     i.draw(g); 
    } 

    public static void main(String[] args){ 
     javax.swing.SwingUtilities.invokeLater(new Runnable(){ 
      public void run(){ 
       javax.swing.JFrame f = new javax.swing.JFrame(); 
       f.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE); 
       try{ 
        f.add(new A()); 
       }catch(Exception e){ 

       } 
       f.pack(); 
       f.setVisible(true); 
      } 
     }); 
    } 
} 

class B{ 

    private final static String META_FORMAT = "javax_imageio_gif_image_1.0"; 
    // instance variables 
    private final BufferedImage[] frames; 
    private BufferedImage master;// Because Gif images can store only the changing 
    // pixels, the first frame is drawn to this image, then the next one *on top of it*, etc. 
    private final short[] frameDurations; // in 100ths of a second 
    private final short[] xOffsets; 
    private final short[] yOffsets; 
    private int frame = 0; 
    private final Dimension size;// the size of the gif (calculated in findSize) 
    private final Timer animationTimer; 

    // constructor from a File (checked to be a gif) 
    public B(File src) throws IOException{ 
     if (!(new MimetypesFileTypeMap().getContentType(src.getPath()).equals("image/gif"))){ 
      throw new IOException("File is not a gif. It's Mime Type is: " + 
       new MimetypesFileTypeMap().getContentType(src.getAbsolutePath())); 
     } 
     FileImageInputStream stream = new FileImageInputStream(src); 
     Iterator<ImageReader> readers = ImageIO.getImageReaders(stream); 
     ImageReader reader = null; 
     // loop through the availible ImageReaders, find one for .gif 
     while (readers.hasNext()){ 
      reader = readers.next(); 
      String metaFormat = reader.getOriginatingProvider().getNativeImageMetadataFormatName(); 
      // if it's a gif 
      if ("gif".equalsIgnoreCase(reader.getFormatName()) && META_FORMAT.equals(metaFormat)){ 
       break; 
      }else{ 
       reader = null; 
       continue; 
      } 
     }// while (readers.hasNext()) 
     // if no reader for gifs was found 
     if (reader == null){ 
      throw new IOException("File could not be read as a gif"); 
     } 
     reader.setInput(stream, false, false); 
     // Lists to be converted to arrays and set as the instance variables 
     ArrayList<BufferedImage> listFrames = new ArrayList<BufferedImage>(); 
     ArrayList<Short> listFrameDurs = new ArrayList<Short>(); 
     ArrayList<Short> listXs = new ArrayList<Short>(); 
     ArrayList<Short> listYs = new ArrayList<Short>(); 
     boolean unknownMeta = false;// asume that the metadata can be read until proven otherwise 
     // loop until there are no more frames (since that isn't known, break needs to be used) 
     for (int i = 0;true;i++){// equivalent of while(true) with a counter 
      IIOImage frame = null; 
      try{ 
       frame = reader.readAll(i, null); 
      }catch(IndexOutOfBoundsException e){ 
       break;// this means theres no more frames 
      } 
      listFrames.add((BufferedImage)frame.getRenderedImage()); 
      if (unknownMeta){// if the metadata has already proven to be unreadable 
       continue; 
      } 
      IIOMetadata metadata = frame.getMetadata(); 
      IIOMetadataNode rootNode = null; 
      try{ 
       rootNode = (IIOMetadataNode) metadata.getAsTree(META_FORMAT); 
      }catch(IllegalArgumentException e){ 
       // means that the metadata can't be read, it's in an unknown format 
       unknownMeta = true; 
       continue; 
      } 
      // get the duration of the current frame 
      IIOMetadataNode graphicControlExt = (IIOMetadataNode)rootNode.getElementsByTagName("GraphicControlExtension").item(0); 
      listFrameDurs.add(Short.parseShort(graphicControlExt.getAttribute("delayTime"))); 
      // get the x and y offsets 
      try{ 
       IIOMetadataNode imageDescrip = (IIOMetadataNode)rootNode.getElementsByTagName("ImageDescriptor").item(0); 
       listXs.add(Short.parseShort(imageDescrip.getAttribute("imageLeftPosition"))); 
       listYs.add(Short.parseShort(imageDescrip.getAttribute("imageTopPosition"))); 
      }catch(IndexOutOfBoundsException e){ 
       e.printStackTrace(); 
       listXs.add((short) 0); 
       listYs.add((short) 0); 
      } 
     }// for loop 
     reader.dispose(); 
     // put the values in the lists into the instance variable arrays 
     frames = listFrames.toArray(new BufferedImage[0]); 
     // looping must be used because the ArrayList can't contian primitives 
     frameDurations = new short[listFrameDurs.size()]; 
     for (int i = 0;i < frameDurations.length;i++){ 
      frameDurations[i] = (short)(listFrameDurs.get(i) * 10); 
     } 
     xOffsets = new short[listXs.size()]; 
     for (int i = 0;i < xOffsets.length;i++){ 
      xOffsets[i] = listXs.get(i); 
     } 
     yOffsets = new short[listYs.size()]; 
     for (int i = 0;i < yOffsets.length;i++){ 
      yOffsets[i] = listYs.get(i); 
     } 
     size = findSize(); 
     animationTimer = new Timer(frameDurations[0], null); 
     clearLayers(); 
    } 

    // finds the size of the image in constructors 
    private final Dimension findSize(){ 
     int greatestX = -1; 
     int greatestY = -1; 
     // loop through the frames and offsets, finding the greatest combination of the two 
     for (int i = 0;i < frames.length;i++){ 
      if (greatestX < frames[i].getWidth() + xOffsets[i]){ 
       greatestX = frames[i].getWidth() + xOffsets[i]; 
      } 
      if (greatestY < frames[i].getHeight() + yOffsets[i]){ 
       greatestY = frames[i].getHeight() + yOffsets[i]; 
      } 
     }// loop 
     return new Dimension(greatestX, greatestY); 
    }// findSize 

    private BufferedImage getFrame(){ 
     /* returning frames[frame] gives a perfect rendering of each frame (but only changed 
     * pixels), but when master is returned, even the first frame shows quality reduction 
     * (seen by slowing down the framerate). The issue is with drawing images to master 
     */ 
     Graphics2D g2d = master.createGraphics(); 
     g2d.drawImage(frames[frame], xOffsets[frame], yOffsets[frame], null); 
     g2d.dispose(); 
     return master; 
    } 

    public Dimension getSize(){ 
     return size; 
    } 

    // adds a FrameChangeListener associated with a component to the Timer 
    public void registerComponent(Component c){ 
     FrameChangeListener l = new FrameChangeListener(c); 
     animationTimer.addActionListener(l); 
     if (!animationTimer.isRunning()){ 
      animationTimer.start(); 
     } 
    } 

    // draws the image to the given Graphics context (registerComponent must be used for the image 
    // to animate properly) 
    public void draw(Graphics g){ 
     g.drawImage(getFrame(), 0, 0, null); 
    } 

    // resets master 
    private void clearLayers(){ 
     master = new BufferedImage((int)size.getWidth(), (int)size.getHeight(), frames[0].getType()); 
    } 

    // class that listens for the Swing Timer. 
    private class FrameChangeListener implements ActionListener{ 

     private final Component repaintComponent; 

     // the Components repaint method will be invoked whenever the animation changes frame 
     protected FrameChangeListener(Component c){ 
      repaintComponent = c; 
     } 

     public void actionPerformed(ActionEvent e){ 
      frame++; 
      int delay; 
      try{ 
       delay = frameDurations[frame] * 10; 
      }catch(ArrayIndexOutOfBoundsException x){ 
       frame = 0; 
       clearLayers(); 
       delay = frameDurations[frame] * 10; 
      } 
      animationTimer.setDelay(delay); 
      repaintComponent.repaint(); 
     }// actionPerformed 

    }// FrameChangeListener 

} 

А вот файл изображения я использовал для теста: enter image description here

И вот как это показывает : enter image description here

было бы весьма признателен, если кто-то может помочь мне решить эту проблему

ответ

3

Проблема эта линия от метода clearLayers():

master = new BufferedImage((int)size.getWidth(), (int)size.getHeight(), frames[0].getType()); 

Как GIF использует палитру, то BufferedImage типа будет TYPE_BYTE_INDEXED. Однако, если вы передадите этот параметр в конструктор BufferedImage, он будет использовать по умолчаниюIndexColorModel (встроенная фиксированная цветовая палитра 256), не палитра из вашего GIF. Таким образом, кадры из GIF должны быть помещены в место назначения, так как цвета не совпадают.

Вместо этого используйте TYPE_INT_RGB/TYPE_INT_ARGB для типа, или использования the constructor that also takes an IndexColorModel parameter и передать IndexColorModel из кадров в формате GIF.

В коде:

master = new BufferedImage((int)size.getWidth(), (int)size.getHeight(), BufferedImage.TYPE_INT_ARGB); 

В качестве альтернативы, следующий должен также работать, если все кадры GIF использует ту же палитру (не обязательно случай):

master = new BufferedImage((int)size.getWidth(), (int)size.getHeight(), frames[0].getType(), (IndexColorModel) frames[0].getColorModel()); 

Однако, как OP сообщает, что последний вариант не работает для него, первый вариант, вероятно, безопаснее. :-)

+1

Большое вам спасибо! Это так долго искажало меня! Ваше первое решение (с использованием 'TYPE_INT_ARGB') отлично работало, но использование конструктора с параметром« IndexColorModel »ничего не изменило. Это не проблема, так как первое решение работает, но я думал, что лучше всего сообщить вам –