# BMP mit ImageIO schreiben



## inflamer (12. Mai 2006)

Abend!

Ich möchte BMPs mit ImageIO schreiben. Soweit kein Problem. Allerdings kommt es mir darauf an, die Auflösung festlegen zu können (dpi). Bei JPEG-Grafiken klappt das bereits auch ohne Probleme:



```
// erstelle BufferedImage und zeichne in seinen grafikkontext
    BufferedImage bi = new BufferedImage(pxlWidth, pxlHeight, BufferedImage.TYPE_INT_RGB);
    Graphics2D g2d = bi.createGraphics();
    // ...
    // ... zeichne in den g2d-kontext
    // ...
    g2d.dispose();
    ImageTypeSpecifier its = new ImageTypeSpecifier(bi.getColorModel(), bi.getSampleModel());
    Iterator iterator = ImageIO.getImageWritersByFormatName("jpg");
    ImageWriter imageWriter = (ImageWriter)iterator.next();
    ImageWriteParam param = imageWriter.getDefaultWriteParam();
    IIOMetadata iomd = imageWriter.getDefaultImageMetadata(its, param);
    initJPGImageMetadata(iomd, resolution);  // <<<--------- aufruf der methode s. unten
    IIOImage iioImage = new IIOImage(bi, null, iomd);

    // schreibe datei
    FileImageOutputStream fios = null;
    try {
      fios = new FileImageOutputStream(file);
      imageWriter.setOutput(fios);
      imageWriter.write(iioImage);
    } catch (Exception e) {
      // ...
    } finally {
      if (fios != null) try { fios.close(); } catch (Exception e) {}
    }
```


```
//----
  private static void initJPGImageMetadata(IIOMetadata iiomd, int resolution) {
    final String resStr = String.valueOf(resolution);
    final String formatName = "javax_imageio_jpeg_image_1.0";
    final Node node = iiomd.getAsTree(formatName);
    final NodeList nodeList = node.getChildNodes();
    for (int i=nodeList.getLength()-1; i>=0; i--) {
      Node n = nodeList.item(i);

      if (n.getNodeName().equals("JPEGvariety")) {
        NodeList childNodes = n.getChildNodes();
        for (int j=childNodes.getLength()-1; j>=0; j--) {
          Node cn = childNodes.item(j);
          if (cn.getNodeName().equals("app0JFIF")) {
            getAttributeByName(cn, "resUnits").setNodeValue("1"); // "dpi"
            getAttributeByName(cn, "Xdensity").setNodeValue(resStr);
            getAttributeByName(cn, "Ydensity").setNodeValue(resStr);
          }
        }
        break;
      }

    }

    try { iiomd.setFromTree(formatName, node);
    } catch (IIOInvalidTreeException e) { /* kann sich nicht ereignen */ }
  }
```


```
//---- hilfsmethode
  private static Node getAttributeByName(Node node, String attributeName) {
    NamedNodeMap nnm = node.getAttributes();
    for (int i=nnm.getLength()-1; i>=0; i--) {
      Node n = nnm.item(i);
      if (n.getNodeName().equals(attributeName))
        return n;
    }
    return null; // no such attribute was found
  }
```


Eine entscheidende Rolle spielt dabei die Klasse IIOMetadata, die quasi alle für ein Grafikformat relevanten Informationen zu einer Grafik enthält - im Fall von BMP und JPEG also auch die Auflösung. Für jedes Grafikformat ist desweiteren eine separate von IIOMetadata abgeleitete Klasse zuständig: bei JPEG-Grafiken ist es z.b. *com.sun.imageio.plugins.jpeg.JPEGMetadata*. Der Hacken bei der entsprechenden BMP-Klasse *com.sun.imageio.plugins.bmp.BMPMetadata* ist nun, dass sich die Attribute darin nicht überschreiben lassen. So werfen die Implementierungen der Schnittstellen-Setter z.B. gleich eine Exception ohne wenn und aber.

Kennt also jemand vielleicht einen anderen Weg?


----------



## inflamer (15. Mai 2006)

eine Lösung:



```
public void writeBMP(OutputStream out, BufferedImage bi, int resolution) throws IOException {
    ImageTypeSpecifier its = new ImageTypeSpecifier(bi.getColorModel(), bi.getSampleModel());
    Iterator iterator = ImageIO.getImageWritersByFormatName("bmp");
    ImageWriter imageWriter = (ImageWriter)iterator.next();
    ImageWriteParam param = imageWriter.getDefaultWriteParam();
    IIOMetadata iiomd = imageWriter.getDefaultImageMetadata(its, param);

    // im header einer bmp-datei steht die auflösung als 32bit-integers in dots per meter,
    // wobei die bytes rückwärts angeordnet sind
    final int dotsPerMeter = Integer.reverseBytes(Math.round((float)(resolution / 0.0254)));

    // outputstream, der die auflösung der bmp-datei korrigiert
    class CorrectingOutputStream extends DataOutputStream {
      private ByteArrayOutputStream baos = new ByteArrayOutputStream(50);
      public CorrectingOutputStream(OutputStream out) {
        super(out);
      }
      public void write(byte b[], int off, int len) throws IOException {
        if (baos != null) {
          baos.write(b, off, len);
          if (baos.size() >= 38) {
            b = baos.toByteArray();
            super.write(b, 0, 38);
            super.writeInt(dotsPerMeter);
            super.writeInt(dotsPerMeter);
            if (b.length > 46)
              super.write(b, 46, b.length - 46);
            baos = null;
          }
        } else { // if baos == null
          super.write(b, off, len);
        }
      }
    }

    // schreibe datei
    imageWriter.setOutput(new MemoryCacheImageOutputStream(new CorrectingOutputStream(out)));
    imageWriter.write(new IIOImage(bi, null, iiomd));
  }
```


----------

