JavaRush /Java блог /Random UA /World of Bytes 1. Робота із зображеннями.
Joysi
41 рівень

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

Стаття з групи Random UA
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); } } } }
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ