JavaRush/Java блог/Архив info.javarush/World of Bytes 1. Работа с изображениями.
Joysi
41 уровень

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

Статья из группы Архив info.javarush
участников
Special for До этого я пояснял на сухих примерах. Попросили работу с изображениями - ловите.
Постановка учебной задачи.
Дан графический файл (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); } } } }
Комментарии (2)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
Elizabeth Ray
Уровень 38
23 октября 2018, 14:06
Полезная статья, всегда было интересно, как устроен фотошоп изнутри)
Joysi
Уровень 41
20 февраля 2016, 16:42
Увеличение резкости
Было

Стало

Добавленный метод (жутко и ужасно неоптимальный, вычисления — все в лоб):
// увеличиваем резкость
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