• ,

World of Bytes 1. Работа с изображениями.

Special for Spiker

До этого я пояснял на сухих примерах. Попросили работу с изображениями — ловите.

Постановка учебной задачи.
Дан графический файл (jpeg, png ...).
Необходимо сделать с ним некие манипуляции и записать результат в другой файл.
Для упрощения рассмотрим три задачи:
— получить негатив изображения
— получить черно-белый вариант изображения (сбросить цветность)
— изменить насыщенность зеленого цвета в изображении.

Заметим, что аналогичным способом мы можем, добавляя новые методы реализовывать и другие задачи:
— увеличить резкость или размытие
— изменить размеры
— повернуть по/против часовой.
— и другие возможность Фотошопа :)
вообщем реализовать любой алгоритм над изображением, насколько у нас хватит фантазии и знания матана (например, распознать количество возможных котиков на картинке).

Немного сухой теории.
Мы рассматриваем растровые изображения (есть еще векторные и другие). То есть, когда файл, помимо собственно заголовка со служебной информацией, хранит прямоугольную матрицу точек. Аналогично экрану современного HD-телевизора у которого разрешение 1920х1080 точек и каждая точка представлена как значения трех составляющих цвета: R(ed), G(reen), B(lue) = Красный, Зеленый и Синий. Эти цвета независимы и данная модель взята из биологии восприятия цвета.
В глазу у нас есть колбочки и палочки. Колбочки трех разновидностей (реагируют на соответствующий одной из трех диапазонов длин волн), палочки «обрабатывают» яркость цвета (амплитуду световой волны).
В модели RGB палочки отвечают за значение составляющей (0 — отсутствие, 255- самый яркий свет), а колбочки — соответственно в какие из R / G / B помещать соответствующую интенсивность.

Например:
Отсутствие света — палочки /колбочки не реагируют и RGB=(0,0,0).
Яркий белый свет — все колбочки реагируют равномерно, палочки фигеют и RGB = (255,255,255).
Серая мышка пробежала — все колбочки реагируют равномерно, палочки реагируют средне и RGB = (127,127,127).
Темно-оранжевый — реагируют R и G палочки, палочки едва откликаются, RGB=(30, 30, 0)


Приступим к практике.
Я писал для примера работы с байтами, поэтому код не вылизан по всем правилам и далеко не оптимален:
мы не проверяем входные параметры, не делаем полноценную проверку ошибок и т.п.
Писалось в лоб, без рефакторинга. Основной упор — работа с байтами-битами.
Напишем, по аналогии задач JavaRush консольную утилиту, которая при вызове в командной строке с соответствующими аргументами модифицирует изображение.

Исходное изображение:
Котенок

вызов с параметрами -n kitten.jpg newkitten.jpg создаст картинку:
негатив

вызов с параметрами -b kitten.jpg newkitten.jpg создаст картинку:
черно-белый котенок

вызов с параметрами -gr kitten.jpg newkitten.jpg создаст картинку:
сумерки

Собственно котд.
package com.joysi.byteworld;

import com.sun.imageio.plugins.jpeg.*;
import com.sun.imageio.plugins.png.*;
import javax.imageio.*;
import javax.imageio.stream.*;
import java.awt.image.BufferedImage;
import java.io.*;

public class image {
    public static void main(String[] args) throws IOException {
        CoolImage picture = new CoolImage(args[1]); // загружаем файл изображения
        if ("-n".equals(args[0]))  picture.convertToNegative();
        if ("-g".equals(args[0]))  picture.addColorGreenChannel(-100);
        if ("-bw".equals(args[0])) picture.convertToBlackAndWhite();
        picture.saveAsJpeg(args[2]);
    }

    public static class CoolImage{
        private int     height;             // высота изображения
        private int     width;              // ширина изображения
        private int[]   pixels;             // собственно массив цветов точек составляющих изображение

        public int getPixel(int x, int y)   { return pixels[y*width+x]; }   // получить пиксель в формате RGB
        public int getRed(int color)        { return color >> 16; }         // получить красную составляющую цвета
        public int getGreen(int color)      { return (color >> 8) & 0xFF; } // получить зеленую составляющую цвета
        public int getBlue(int color)       { return color  & 0xFF;}        // получить синюю   составляющую цвета

        // Конструктор - создание изображения из файла
        public CoolImage(String fileName) throws IOException {
            BufferedImage img = readFromFile(fileName);
            this.height = img.getHeight();
            this.width  = img.getWidth();
            this.pixels = copyFromBufferedImage(img);
        }

        // Чтение изображения из файла в BufferedImage
        private BufferedImage readFromFile(String fileName) throws IOException {
            ImageReader     r  = new JPEGImageReader(new JPEGImageReaderSpi());
            r.setInput(new FileImageInputStream(new File(fileName)));
            BufferedImage  bi = r.read(0, new ImageReadParam());
            ((FileImageInputStream) r.getInput()).close();
            return bi;
        }

        // Формирование BufferedImage из массива pixels
        private BufferedImage copyToBufferedImage()  {
            BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            for (int i = 0; i < height; i++)
                for (int j = 0; j < width; j++)
                    bi.setRGB(j, i, pixels[i*width +j]);
            return bi;
        }

        // Формирование массива пикселей из BufferedImage
        private int[] copyFromBufferedImage(BufferedImage bi)  {
            int[] pict = new int[height*width];
            for (int i = 0; i < height; i++)
                for (int j = 0; j < width; j++)
                    pict[i*width + j] = bi.getRGB(j, i) & 0xFFFFFF; // 0xFFFFFF: записываем только 3 младших байта RGB
            return pict;
        }

        // Запись изображения в jpeg-формате
        public void saveAsJpeg(String fileName) throws IOException {
            ImageWriter writer = new JPEGImageWriter(new JPEGImageWriterSpi());
            saveToImageFile(writer, fileName);
        }

        // Запись изображения в png-формате (другие графические форматы по аналогии)
        public void saveAsPng(String fileName) throws IOException {
            ImageWriter writer = new PNGImageWriter(new PNGImageWriterSpi());
            saveToImageFile(writer, fileName);
        }

        // Собственно запись файла (общая для всех форматов часть).
        private void saveToImageFile(ImageWriter iw, String fileName) throws IOException {
            iw.setOutput(new FileImageOutputStream(new File(fileName)));
            iw.write(copyToBufferedImage());
            ((FileImageOutputStream) iw.getOutput()).close();
        }

        // конвертация изображения в негатив
        public void  convertToNegative() {
            for (int i = 0; i < height; i++)
                for (int j = 0; j < width; j++)
                    // Применяем логическое отрицание и отбрасываем старший байт
                    pixels[i*width + j] = ~pixels[i*width + j] & 0xFFFFFF;
        }

        // конвертация изображения в черно-белый вид
        public void convertToBlackAndWhite() {
            for (int i = 0; i < height; i++)
                for (int j = 0; j < width; j++) {
                    // находим среднюю арифметическую интенсивность пикселя по всем цветам
                    int intens = (getRed(pixels[i * width + j]) +
                                getGreen(pixels[i * width + j]) +
                                 getBlue(pixels[i * width + j])) / 3;
                    // ... и записываем ее в каждый цвет за раз , сдвигая байты RGB на свои места
                    pixels[i * width + j] = intens + (intens << 8) + (intens << 16);
                }
        }

        // изменяем интесивность зеленого цвета
        public void addColorGreenChannel(int delta) {
            for (int i = 0; i < height; i++)
                for (int j = 0; j < width; j++) {
                    int newGreen =  getGreen(pixels[i * width + j]) + delta;
                    if (newGreen > 255) newGreen=255;  // Отсекаем при превышении границ байта
                    if (newGreen < 0)   newGreen=0;
                    // В итоговом пикселе R и B цвета оставляем без изменений: & 0xFF00FF
                    // Полученный новый G (зеленый) засунем в "серединку" RGB: | (newGreen << 8)
                    pixels[i * width + j] = pixels[i * width + j] & 0xFF00FF | (newGreen << 8);
                }
        }
    }
}

1 комментарий

Joysi
  • Joysi
  • +1
  • Комментарий отредактирован 2016-02-20 16:42:22 пользователем Joysi
Увеличение резкости
Было

Стало

Добавленный метод (жутко и ужасно неоптимальный, вычисления — все в лоб):
// увеличиваем резкость
public void addSharpen() {
    // Чтобы работать с неизмененными данными скопируем в новый массив
    int[] arrnew= Arrays.copyOf(pixels, width*height);

    for (int j = 1; j < height-1; j++)
        for (int i = 1; i < width-1; i++) {
            // покомпонентно применяем фильтр усиления резкости
            //  -0.1 -0.1 -0.1
            //  -0.1  1.8 -0.1
            //  -0.1 -0.1 -0.1
            int newRed=getRed(getPixel(i,j))*18/10 -
               (getRed(getPixel(i-1,j-1)) + getRed(getPixel(i-1,j)) + getRed(getPixel(i-1,j+1)) +
                 getRed(getPixel(i,j-1))   + getRed(getPixel(i,j+1)) +
                 getRed(getPixel(i+1,j-1)) + getRed(getPixel(i+1,j)) + getRed(getPixel(i+1,j+1)))/10;
            if (newRed > 255) newRed=255;  // Отсекаем при превышении границ байта
            if (newRed < 0)   newRed=0;
            
            int newGreen=getGreen(getPixel(i,j))*18/10 -
               (getGreen(getPixel(i-1,j-1)) + getGreen(getPixel(i-1,j)) + getGreen(getPixel(i-1,j+1)) +
                 getGreen(getPixel(i,j-1))   + getGreen(getPixel(i,j+1)) +
                 getGreen(getPixel(i+1,j-1)) + getGreen(getPixel(i+1,j)) + getGreen(getPixel(i+1,j+1)))/10;
            if (newGreen > 255) newGreen=255;  // Отсекаем при превышении границ байта
            if (newGreen < 0)   newGreen=0;

            int newBlue=getBlue(getPixel(i,j))*18/10 -
                (getBlue(getPixel(i-1,j-1)) + getBlue(getPixel(i-1,j)) + getBlue(getPixel(i-1,j+1)) +
                 getBlue(getPixel(i,j-1))   + getBlue(getPixel(i,j+1)) +
                 getBlue(getPixel(i+1,j-1)) + getBlue(getPixel(i+1,j)) + getBlue(getPixel(i+1,j+1)))/10;
            if (newBlue > 255) newBlue=255;  // Отсекаем при превышении границ байта
            if (newBlue < 0)   newBlue=0;

            arrnew[j * width + i] = newBlue + (newGreen << 8) + (newRed << 16);
        }
    pixels = arrnew;
}
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.