Подтвердить что ты не робот

Как объединить несколько PNG в один большой файл PNG?

У меня ок. 6000 PNG файлов (256 * 256 пикселей) и хотите объединить их в большой PNG, сохраняя их программно.

Какой лучший/самый быстрый способ сделать это?

(Целью является печать на бумаге, поэтому использование какой-либо веб-технологии не является опцией, и наличие одного файла с одним изображением устранит многие ошибки использования.)

Я попробовал fahd-предложение, но получаю NullPointerException, когда пытаюсь создать BufferedImage с шириной 24576 пикселей и высотой 15360 пикселей. Любые идеи?

4b9b3361

Ответ 1

Создайте большое изображение, на которое вы будете писать. Разработайте его размеры в зависимости от количества строк и столбцов, которые вы хотите.

    BufferedImage result = new BufferedImage(
                               width, height, //work these out
                               BufferedImage.TYPE_INT_RGB);
    Graphics g = result.getGraphics();

Теперь прокрутите изображения и нарисуйте их:

    for(String image : images){
        BufferedImage bi = ImageIO.read(new File(image));
        g.drawImage(bi, x, y, null);
        x += 256;
        if(x > result.getWidth()){
            x = 0;
            y += bi.getHeight();
        }
    }

Наконец напишите в файл:

    ImageIO.write(result,"png",new File("result.png"));

Ответ 2

Я не вижу, как это было бы возможно "без обработки и повторного кодирования". Если вы настаиваете на использовании Java, я просто предлагаю вам использовать JAI (страница проекта здесь). С этим вы создаете один большой BufferedImage, загружать меньшие изображения и нарисуйте их на более крупном.

Или просто используйте ImageMagick montage:

montage *.png output.png

Для получения дополнительной информации о montage см. usage.

Ответ 3

У меня была некоторая аналогичная потребность некоторое время назад (огромные изображения - и, я, мой случай с 16 бит-тьфутом, чтобы иметь их полностью в памяти, не было вариантом). И я закончил кодирование библиотеки PNG, чтобы сделать чтение/запись последовательным образом. Если кто-то найдет это полезным, здесь.

Обновлено: здесь пример кода:

/**
 * Takes several tiles and join them in a single image
 * 
 * @param tiles            Filenames of PNG files to tile
 * @param dest            Destination PNG filename
 * @param nTilesX            How many tiles per row?
 */
public class SampleTileImage {

        public static void doTiling(String tiles[], String dest, int nTilesX) {
                int ntiles = tiles.length;
                int nTilesY = (ntiles + nTilesX - 1) / nTilesX; // integer ceil
                ImageInfo imi1, imi2; // 1:small tile   2:big image
                PngReader pngr = new PngReader(new File(tiles[0]));
                imi1 = pngr.imgInfo;
                PngReader[] readers = new PngReader[nTilesX];
                imi2 = new ImageInfo(imi1.cols * nTilesX, imi1.rows * nTilesY, imi1.bitDepth, imi1.alpha, imi1.greyscale,
                                imi1.indexed);
                PngWriter pngw = new PngWriter(new File(dest), imi2, true);
                // copy palette and transparency if necessary (more chunks?)
                pngw.copyChunksFrom(pngr.getChunksList(), ChunkCopyBehaviour.COPY_PALETTE
                                | ChunkCopyBehaviour.COPY_TRANSPARENCY);
                pngr.readSkippingAllRows(); // reads only metadata             
                pngr.end(); // close, we'll reopen it again soon
                ImageLineInt line2 = new ImageLineInt(imi2);
                int row2 = 0;
                for (int ty = 0; ty < nTilesY; ty++) {
                        int nTilesXcur = ty < nTilesY - 1 ? nTilesX : ntiles - (nTilesY - 1) * nTilesX;
                        Arrays.fill(line2.getScanline(), 0);
                        for (int tx = 0; tx < nTilesXcur; tx++) { // open several readers
                                readers[tx] = new PngReader(new File(tiles[tx + ty * nTilesX]));
                                readers[tx].setChunkLoadBehaviour(ChunkLoadBehaviour.LOAD_CHUNK_NEVER);
                                if (!readers[tx].imgInfo.equals(imi1))
                                        throw new RuntimeException("different tile ? " + readers[tx].imgInfo);
                        }
                        for (int row1 = 0; row1 < imi1.rows; row1++, row2++) {
                                for (int tx = 0; tx < nTilesXcur; tx++) {
                                        ImageLineInt line1 = (ImageLineInt) readers[tx].readRow(row1); // read line
                                        System.arraycopy(line1.getScanline(), 0, line2.getScanline(), line1.getScanline().length * tx,
                                                        line1.getScanline().length);
                                }
                                pngw.writeRow(line2, row2); // write to full image
                        }
                        for (int tx = 0; tx < nTilesXcur; tx++)
                                readers[tx].end(); // close readers
                }
                pngw.end(); // close writer
        }

        public static void main(String[] args) {
                doTiling(new String[] { "t1.png", "t2.png", "t3.png", "t4.png", "t5.png", "t6.png" }, "tiled.png", 2);
                System.out.println("done");
        }
}

Ответ 4

Формат PNG не поддерживает плитку, поэтому вы не можете избежать по крайней мере декомпрессии и повторного сжатия потока данных. Если палитры всех изображений идентичны (или все отсутствуют), это единственное, что вам действительно нужно делать. (Я также предполагаю, что изображения не чересстрочны.)

Вы можете сделать это потоковым способом, имея только одну "строку" PNG за раз, считывая куски соответствующего размера из своего потока данных и записывая их в выходной поток. Таким образом, вам не нужно будет хранить целые изображения в памяти. Наиболее эффективным способом было бы программировать это поверх самого libpng. Возможно, вам понадобится сохранить несколько более чем одну линию сканирования пикселей в памяти из-за предсказания пикселей.

Но просто используя утилиты командной строки ImageMagick, netpbm или аналогичные, вы сэкономите много времени на разработку, для чего может быть небольшое увеличение.

Ответ 5

Как указывали другие, использование Java не обязательно является лучшим выбором здесь.

Если вы собираетесь использовать Java, лучше всего - если у вас недостаточно памяти, чтобы вы не могли читать весь набор данных в памяти несколько раз, а затем записывать его снова - это реализовать RenderedImage с классом, который будет считывать ваши PNG с диска по требованию. Если вы просто создаете свой собственный новый BufferedImage и затем попытаетесь записать его, создатель PNG создаст дополнительную копию данных. Если вы создадите свой собственный RenderedImage, вы можете передать его в ImageIO.write(myImageSet,"png",myFileName). Вы можете скопировать SampleModel и ColorModel информацию из вашего первого PNG - надеюсь, что они все одинаковы.

Если вы делаете вид, что для всего изображения используется несколько фрагментов (одна плитка для исходного изображения), то ImageIO.write создаст WritableRaster, который является размером всего набора данных изображения, и вызовет вашу реализацию RenderedImage.copyData чтобы заполнить его данными. Если у вас достаточно памяти, это простой способ (потому что вы получаете огромный целевой набор данных и можете просто сбрасывать все ваши данные изображения в него - с помощью метода setRect(dx,dy,Raster)), а затем не нужно беспокоиться об этом снова). Я не проверял, сохраняет ли это память, но мне кажется, что она должна.

В качестве альтернативы, если вы притворяетесь, что все изображение является одним фрагментом, ImageIO.write затем спросит, используя getTile(0,0), для растра, соответствующего этому всему изображению. Поэтому вам нужно создать свой собственный растровый редактор, который, в свою очередь, позволит вам создать свой собственный DataBuffer. Когда я пробовал этот подход, минимальное использование памяти, которое успешно написало PNG 15360x25600 RGB, было -Xmx1700M (в Scala, кстати), которое составляет едва более 4 байтов на пиксель написанного изображения, так что очень мало накладных расходов выше одного полное изображение в памяти.

Формат данных PNG сам по себе не тот, который требует полного изображения в памяти - он будет работать нормально в кусках, но, к сожалению, реализация PNG-образа по умолчанию предполагает, что в памяти будет весь массив пикселей.

Ответ 6

Комбинирование изображений

private static void combineALLImages(String screenNames, int screens) throws IOException, InterruptedException {
    System.out.println("screenNames --> D:\\screenshots\\screen   screens --> 0,1,2 to 10/..");
    int rows = screens + 1;
    int cols = 1;
    int chunks = rows * cols ; 

     File[] imgFiles = new File[chunks];
    String files = "";
    for (int i = 0; i < chunks; i++) {
        files = screenNames + i + ".jpg";
        imgFiles[i] = new File(files);          
        System.out.println(screenNames + i + ".jpg"+"\t Screens : "+screens);    

    }

    BufferedImage sample = ImageIO.read(imgFiles[0]);
    //Initializing the final image
    BufferedImage finalImg = new BufferedImage(sample.getWidth() * cols, sample.getHeight() * rows, sample.getType());

    int index = 0;
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            BufferedImage temp = ImageIO.read(imgFiles[index]);
            finalImg.createGraphics().drawImage(temp, sample.getWidth() * j, sample.getHeight() * i, null);
            System.out.println(screenNames + index + ".jpg");
            index++;
        }
    }
    File final_Image = new File("D:\\Screenshots\\FinalImage.jpg");
    ImageIO.write(finalImg, "jpeg", final_Image);

}

Ответ 7

простой python script для объединения фрагментов в одно большое изображение:

import Image

TILESIZE = 256
ZOOM = 15
def merge_images( xmin, xmax, ymin, ymax, output) :
    out = Image.new( 'RGB', ((xmax-xmin+1) * TILESIZE, (ymax-ymin+1) * TILESIZE) ) 

    imx = 0;
    for x in range(xmin, xmax+1) :
        imy = 0
        for y in range(ymin, ymax+1) :
            tile = Image.open( "%s_%s_%s.png" % (ZOOM, x, y) )
            out.paste( tile, (imx, imy) )
            imy += TILESIZE
        imx += TILESIZE

    out.save( output )

пробег:

merge_images(18188, 18207, 11097, 11111, "output.png")

работает для файлов с именем% ZOOM_% XCORD_% YCORD.png, например 15_18188_11097.png

Ответ 8

Используйте imagemagick montage следующим образом:

montage *.png montage.png

Более подробную информацию о параметрах можно найти здесь

Удачи.

Ответ 9

Вам может быть лучше отрываться от другого (без потерь) формата изображения. PPM мертв, легко использовать (и поместить плитки программно, это просто большой массив на диске, поэтому вам нужно будет только сохранить его ряд плит не более), но он очень расточительно пространства (12 байт на пиксель!).

Затем используйте стандартный конвертер (например, ppm2png), который принимает промежуточный формат и превращает его в гигантский PNG.