BMP mit ImageIO schreiben

Status
Nicht offen für weitere Antworten.

inflamer

Bekanntes Mitglied
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:


Code:
    // 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) {}
    }

Code:
  //----
  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 */ }
  }
Code:
  //---- 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?
 
eine Lösung:


Code:
  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));
  }
 
Status
Nicht offen für weitere Antworten.

Zurück
Oben