获取子图像的像素

Get Pixels of SubImage

考虑以下简单的 Clojure 与 Java 互操作以读取图像的像素数据:

(import java.awt.image.BufferedImage)
(import javax.imageio.ImageIO)
(require '[clojure.java.io :as io])

(defn read-image [path]
  (ImageIO/read (io/file path)))

(defn get-sub-image [img x y w h]
  (.getSubimage img x y w h))

;; this assumes that images have no fourth alpha channel:
(defn get-pixels
  ([img x y w h] (get-pixels (get-sub-image img x y w h)))
  ([img] (let [bytes (-> img .getRaster .getDataBuffer .getData)]
           (map vec (partition 3 bytes)))))

这对于获取整个图像的像素效果很好,如下所示:

(def all-pixels (get-pixels (read-image "path/to/img.jpg")))
(nth all-pixels 0)
;; [34 56 7]
(count all-pixels)
;; 122343

但是,当尝试使用附加坐标参数调用 get-pixels 时,结果仍然包含整个数据:

(def some-pixels (get-pixels (read-image "path/to/img.jpg") 0 0 2 2))
(count some-pixels)
;; 122343

这里我希望只接收到 4 个像素。漏洞在哪里?

也欢迎对将图像数据处理为惰性序列的一般方法提出任何意见。

阅读代码有点困难,但如果我们浏览源代码:

    public BufferedImage More ...getSubimage (int x, int y, int w, int h) {
        return new BufferedImage (colorModel,
                                  raster.createWritableChild(x, y, w, h,
                                                             0, 0, null),
                                  colorModel.isAlphaPremultiplied(),
                                  properties);
   }

这个方法调用raster.createWritableChild 让我们看看...

 public WritableRaster More ...createWritableChild(int parentX, int parentY,
                                              int w, int h,
                                             int childMinX, int childMinY,
                                             int bandList[]) {
        if (parentX < this.minX) {
            throw new RasterFormatException("parentX lies outside raster");
        }
        if (parentY < this.minY) {
            throw new RasterFormatException("parentY lies outside raster");
        }
        if ((parentX+w < parentX) || (parentX+w > this.width + this.minX)) {
            throw new RasterFormatException("(parentX + width) is outside raster");
        }
        if ((parentY+h < parentY) || (parentY+h > this.height + this.minY)) {
            throw new RasterFormatException("(parentY + height) is outside raster");
        }

        SampleModel sm;
        // Note: the SampleModel for the child Raster should have the same
        // width and height as that for the parent, since it represents
        // the physical layout of the pixel data.  The child Raster's width
        // and height represent a "virtual" view of the pixel data, so
        // they may be different than those of the SampleModel.
        if (bandList != null) {
            sm = sampleModel.createSubsetSampleModel(bandList);
        }
        else {
            sm = sampleModel;
        }

        int deltaX = childMinX - parentX;
        int deltaY = childMinY - parentY;

        return new WritableRaster(sm,
                                  getDataBuffer(),
                                  new Rectangle(childMinX,childMinY,
                                                w, h),
                                  new Point(sampleModelTranslateX+deltaX,
                                            sampleModelTranslateY+deltaY),
                                 this);
    }

如您所见,创建的新栅格与其父栅格具有相同的 DataBuffer(可能允许在更新子图像时修改整个图像)所以当您执行

img .getRaster .getDataBuffer

你得到了整个图像的数据缓冲区。

我当然没有测试,但 BufferedImage.getData(Rectangle rect) 应该 return 一个有自己的 DataBuffer 的新栅格,然后像你那样做

参见 http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/awt/image/BufferedImage.java#BufferedImage.getData%28%29

编辑:Anton Harald (op) 的最终测试答案

(defn get-data [img x y w h] 
  (-> (.getData img (Rectangle. x y w h)) 
      .getDataBuffer 
      .getData))