Робота з комп'ютерною графікою спирається на програмування графічних ефектів. При вивченні даного курсу ми не використовуємо якусь конкретну мову програмування, але все ж,
потрібно на чомусь програмувати.
У даному додатку містяться приклади коду на різних мовах, які реалізують одну й ту ж задачу - перетворення кольорового зображення в градації сірого (неправильно кажучи - в чорно-біле).
А вже відштовхуючись від відомого коду, нескладно реалізувати той чи інший графічний ефект.
Для перетворення зображення з повнокольорового у градації сірого, існує багато різних формул. Найпростіша являє собою середнє арифметичне базових кольорів:
\[
Gray=\frac{1}{3}(Red+Green+Blue),
\]
після чого кожному кольору присвоюємо отримане сіре значення
\[
Red=Gray,\\
Green=Gray,\\
Blue=Gray.
\]
Досить поважна організація
The International Commission on Illumination, скорочено, CIE від французьского Commission Internationale de l´Eclairage
пропонує наступний підхід
\[
Gray = 0.2126 × Red + 0.7152 × Green + 0.0722 × Blue.
\]
Часто для цієї мети в якості компоненти gray використовується люмінісцентна складова Y колірного простору YCrCb, що використовується у популярному форматі JPEG2000:
\[
Y=\frac{1}{23}(7 Red+ 14 Green +2 Blue)= \\
0.3043× Red +
0.08696× Blue+
0.608696× Green,\\
Cb=-\frac{4}{23}(Red+ 2 Green- 3 Blue),\\
Cr=\frac{4}{69}(8Red-7 Green -Blue),\\
\]
і зворотні
\[
Red=Y+\frac{3}{2}Cr,\\
Green=Y-\frac{1}{4}(3 Cr+Cb),\\
Blue=Y+\frac{7}{4}Cb.\\
\]
Результат роботи буде мати вигляд
Первісне зображення | У градаціях сірого |
| |
Зауваження. Код на мові С працює тільки з 24-бітними зображеннями формату *.bmp. У зв'язку з цим, на початку робиться розбір заголовку файлу і заголовку bitmap.
Докладно структура *.bmp описана у разділі Методи стиску зображень.
C
Файл "gray.c"
// file: gray.c
// RGB -> YUV -> Gray
#include "gray.h"
void main(int argc, char* argv[])
{
if (argc<3) {puts("Usage: Gray f1 f2\nf1 - input\n"
"f2 - output");return;}
if(!CopyFile(argv[1],argv[2],FALSE)){
puts("Error during file operation");
return;
}
hInFile=CreateFile(argv[2],GENERIC_WRITE|GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL|FILE_FLAG_SEQUENTIAL_SCAN,NULL);
if (hInFile == INVALID_HANDLE_VALUE){
puts("Error during file creation");
return;
}
hMapFile=CreateFileMapping(hInFile,NULL,PAGE_READWRITE,0,0,NULL);
if (hMapFile==NULL)goto L0;
pFile=MapViewOfFile(hMapFile,FILE_MAP_WRITE,0,0,0);
if (pFile==NULL) goto L1;
pFH = (PBITMAPFILEHEADER)pFile;
pIH = (PBITMAPINFOHEADER)(pFile+14);
if (pFH->bfType!=0x4d42 || pIH->biBitCount!=24)
{
puts("Invalid File Format");
goto L2;
}
pData = pFile+54; //bgr
bWidth=pIH->biWidth;
bHeight=pIH->biHeight;
bWidthR=bWidth*3;
bWidthR = bWidthR+(4-bWidthR%4)%4;
for(i=0;i<bWidth;i++)
{
for(j=0;j<bHeight;j++)
{
r = (pData+3*i+3*j*bWidth+2);
g = (pData+3*i+3*j*bWidth+1);
b = (pData+3*i+3*j*bWidth);
y = (7*(*r)+2*(*b)+14*(*g))/23;
cb = cut(-4*((*r)-3*(*b)+2*(*g))/23,-128,127);
cr = 4*(8*(*r)-(*b)-7*(*g))/69;
*r = y;//cut(y+cr*3/2,0,255);
*g = y;// cut(y-(cb+3*cr)/4,0,255);
*b = y;// cut(y+7*cb/4,0,255);
}
}
FlushViewOfFile(pFile,0);
L2:
UnmapViewOfFile(pFile);
L1:
CloseHandle(hMapFile);
L0:
CloseHandle(hInFile);
return;
}
Файл "gray.h"
#ifndef __GRAY_H
#define __GRAY_H
#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <math.h>
#define FCB(v) ((85*(v)-2)/89)
#define FCR(v) ((85*(v)+3)/79)
#define FR(v) (255*((v)+192)/637)
#define FG(v) (((v)+127)/2)
#define FB(v) (255*((v)+224)/701)
typedef unsigned char uatom8;
typedef unsigned short uatom16;
typedef unsigned long uatom32;
typedef char satom8;
typedef short satom16;
typedef long satom32;
PBITMAPFILEHEADER pFH;
PBITMAPINFOHEADER pIH;
HANDLE hInFile,hMapFile;
uatom8 *pData,*pFile,*r,*g,*b;
DWORD bWidth,bHeight,bWidthR;
INT32 i,j,y,cr,cb,R,G,B;
// Main Routines
satom32 cut(satom32 e,satom32 a,satom32 b);
satom32 cut(satom32 e,satom32 a,satom32 b)
{
satom32 r;
r = (e<a)?a:e;
return (r>b ? b:r);
}
#endif //Gray
C#
Для мови програмування C # запропоновано два різних методи. Перший з яких безпосередньо читає зображення попіксельно. Для вивчення методів комп'ютерної графіки цього цілком достатньо.
Bitmap tImage = new Bitmap("Lena.bmp");
for (int x = 0; x < tImage.Width; x++)
{
for (int y = 0; y < tImage.Height; y++)
{
Color tCol = tImage.GetPixel(x, y);
// L = 0.2126·R + 0.7152·G + 0.0722·B
double L = 0.2126 * tCol.R + 0.7152 * tCol.G + 0.0722 * tCol.B;
tImage.SetPixel(x, y, Color.FromArgb(Convert.ToInt32(L), Convert.ToInt32(L), Convert.ToInt32(L)));
}
}
// Save
tImage.Save("Lena_gray.bmp");
Другий - професійний, заснований на читанні пам'яті з використанням покажчиків. У цьому випадку використовується небезпечний режим роботи (unsafe mode), читання-запис проходить максимально швидко.
using System;
using System.Drawing;
using System.Drawing.Imaging;
namespace BitmapLib
{
public unsafe class Gray
{
public byte[]R;
public byte[]G;
public byte[]B;
public int W;
public int H;
public Gray(string filename)
{
Bitmap bmp=new Bitmap(filename);
BitmapData bd=bmp.LockBits(new Rectangle(0,0,bmp.Width,bmp.Height),ImageLockMode.ReadOnly,PixelFormat.Format24bppRgb);
W=bmp.Width;
H=bmp.Height;
R=new byte[W*H];
G=new byte[W*H];
B=new byte[W*H];
try
{
byte *curpos=(byte*)bd.Scan0;
for (int j=0;j$lt;H;j++)
{
curpos=(byte*)bd.Scan0+j*bd.Stride;
for (int i=0;i<W;i++)
{
B[i+j*W]=(*curpos++);
G[i+j*W]=(*curpos++);
R[i+j*W]=(*curpos++);
}
}
}
finally
{
bmp.UnlockBits(bd);
}
}
public Bitmap GrayScale()
{
Bitmap bmp=new Bitmap(W,H,PixelFormat.Format24bppRgb);
BitmapData bd=bmp.LockBits(new Rectangle(0,0,bmp.Width,bmp.Height),ImageLockMode.WriteOnly,PixelFormat.Format24bppRgb);
try
{
byte *curpos=(byte*)bd.Scan0;
for (int j=0;j<H;j++)
{
curpos=(byte*)bd.Scan0+j*bd.Stride;
for (int i=0;i<W;i++)
{
(*curpos++)=(byte)((B[i+j*W]+G[i+j*W]+R[i+j*W])/3);
(*curpos++)=(byte)((B[i+j*W]+G[i+j*W]+R[i+j*W])/3);
(*curpos++)=(byte)((B[i+j*W]+G[i+j*W]+R[i+j*W])/3);
}
}
}
finally
{
bmp.UnlockBits(bd);
}
return bmp;
}
}
}
Go
package raster
import (
"math"
"math/rand"
)
// Grmap parallels Bitmap, but with an element type of uint16
// in place of Pixel.
type Grmap struct {
Comments []string
rows, cols int
px []uint16
pxRow [][]uint16
}
// NewGrmap constructor.
func NewGrmap(x, y int) (b *Grmap) {
g := &Grmap{
Comments: []string{creator}, // creator a const in bitmap source file
rows: y,
cols: x,
px: make([]uint16, x*y),
pxRow: make([][]uint16, y),
}
x0, x1 := 0, x
for i := range g.pxRow {
g.pxRow[i] = g.px[x0:x1]
x0, x1 = x1, x1+x
}
return g
}
func (b *Grmap) Extent() (cols, rows int) {
return b.cols, b.rows
}
func (g *Grmap) Fill(c uint16) {
for i := range g.px {
g.px[i] = c
}
}
func (g *Grmap) SetPx(x, y int, c uint16) bool {
defer func() { recover() }()
g.pxRow[y][x] = c
return true
}
func (g *Grmap) GetPx(x, y int) (uint16, bool) {
defer func() { recover() }()
return g.pxRow[y][x], true
}
// Grmap method of Bitmap, converts (color) Bitmap to (grayscale) Grmap
func (b *Bitmap) Grmap() *Grmap {
g := NewGrmap(b.cols, b.rows)
g.Comments = append([]string{}, b.Comments...)
for i, p := range b.px {
g.px[i] = uint16((int64(p.R)*2126 + int64(p.G)*7152 + int64(p.B)*722) *
math.MaxUint16 / (math.MaxUint8 * 10000))
}
return g
}
// Bitmap method Grmap, converts Grmap to Bitmap. All pixels in the resulting
// color Bitmap will be (very nearly) shades of gray.
func (g *Grmap) Bitmap() *Bitmap {
b := NewBitmap(g.cols, g.rows)
b.Comments = append([]string{}, g.Comments...)
for i, p := range g.px {
roundedSum := int(p) * 3 * math.MaxUint8 / math.MaxUint16
rounded := uint8(roundedSum / 3)
remainder := roundedSum % 3
b.px[i].R = rounded
b.px[i].G = rounded
b.px[i].B = rounded
if remainder > 0 {
odd := rand.Intn(3)
switch odd + (remainder * 3) {
case 3:
b.px[i].R++
case 4:
b.px[i].G++
case 5:
b.px[i].B++
case 6:
b.px[i].G++
b.px[i].B++
case 7:
b.px[i].R++
b.px[i].B++
case 8:
b.px[i].R++
b.px[i].G++
}
}
}
return b
}
Java
import javax.imageio.ImageIO;
import java.io.File;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.IOException;
public class gray {
public static void main(String[] args) {
try {
// відкриваємо зображення
File file = new File("Lena.jpg");
BufferedImage source = ImageIO.read(file);
// Створюємо нове зображення, такого ж розміру
BufferedImage result = new BufferedImage(source.getWidth(), source.getHeight(), source.getType());
// Проводимо обробку кожного пікселя
for (int x = 0; x < source.getWidth(); x++) {
for (int y = 0; y < source.getHeight(); y++) {
// Отримуємо колір поточного пікселя
Color color = new Color(source.getRGB(x, y));
// Отримуємо складові поточного кольору
int blue = color.getBlue();
int red = color.getRed();
int green = color.getGreen();
// Отримуємо компоненту сірого
int grey = (int) (red * 0.299 + green * 0.587 + blue * 0.114);
int newRed = grey;
int newGreen = grey;
int newBlue = grey;
// Створюємо новий колір
Color newColor = new Color(newRed, newGreen, newBlue);
// встановлюємо цей колір у поточний піксель результуючого зображення
result.setRGB(x, y, newColor.getRGB());
}
}
// Зберігаємо результат у новий файл
File output = new File("Lena_grey.jpg");
ImageIO.write(result, "jpg", output);
} catch (IOException e) {
System.out.println("Файл не знайдено або не вдалося зберегти");
}
}
}
JavaScript
function toGray(img) {
let cnv = document.getElementById("canvas");
let ctx = cnv.getContext('2d');
let imgW = img.width;
let imgH = img.height;
cnv.width = imgW;
cnv.height = imgH;
ctx.drawImage(img, 0, 0);
let pixels = ctx.getImageData(0, 0, imgW, imgH);
for (let y = 0; y < pixels.height; y ++) {
for (let x = 0; x < pixels.width; x ++) {
let i = (y * 4) * pixels.width + x * 4;
let avg = (pixels.data[i] + pixels.data[i + 1] + pixels.data[i + 2]) / 3;
pixels.data[i] = avg;
pixels.data[i + 1] = avg;
pixels.data[i + 2] = avg;
}
}
ctx.putImageData(pixels, 0, 0, 0, 0, pixels.width, pixels.height);
return cnv.toDataURL();
}
Kotlin
// version 1.2.10
import java.io.File
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
fun BufferedImage.toGrayScale() {
for (x in 0 until width) {
for (y in 0 until height) {
var argb = getRGB(x, y)
val alpha = (argb shr 24) and 0xFF
val red = (argb shr 16) and 0xFF
val green = (argb shr 8) and 0xFF
val blue = argb and 0xFF
val lumin = (0.2126 * red + 0.7152 * green + 0.0722 * blue).toInt()
argb = (alpha shl 24) or (lumin shl 16) or (lumin shl 8) or lumin
setRGB(x, y, argb)
}
}
}
fun main(args: Array<String>) {
val image = ImageIO.read(File("Lena.jpg")) // using BBC BASIC image
image.toGrayScale()
val grayFile = File("Lena_gray.jpg")
ImageIO.write(image, "jpg", grayFile)
}
Perl
#! /usr/bin/perl
use strict;
use Image::Imlib2;
sub tograyscale
{
my $img = shift;
my $gimg = Image::Imlib2->new($img->width, $img->height);
for ( my $x = 0; $x < $gimg->width; $x++ ) {
for ( my $y = 0; $y < $gimg->height; $y++ ) {
my ( $r, $g, $b, $a ) = $img->query_pixel($x, $y);
my $gray = int(0.2126 * $r + 0.7152 * $g + 0.0722 * $b);
# discard alpha info...
$gimg->set_color($gray, $gray, $gray, 255);
$gimg->draw_point($x, $y);
}
}
return $gimg;
}
my $animage = Image::Imlib2->load("Lena.jpg");
my $gscale = tograyscale($animage);
$gscale->set_quality(80);
$gscale->save("Lena_gray.jpg");
exit 0;
PHP
$file = "Lena.jpg";
$img = ImageCreateFromJpeg($file);
$imw = imagesx($img);
$imh = imagesy($img);
for ($i=0; $i<$imw; $i++)
{
for ($j=0; $j<$imh; $j++)
{
//collect the rgb value from the current pixel of image
$rgb = ImageColorAt($img, $i, $j);
// extract r,g,b value separately
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
// get value from rgb scale
$gr = round(($r + $g + $b) / 3);
// gray values have r=g=b=gr
$val = imagecolorallocate($img, $gr, $gr, $gr);
// set the gray value
imagesetpixel ($img, $i, $j, $val);
}
}
header('Content-type: image/jpeg');
imagejpeg($img);
Python
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from PIL import Image, ImageDraw
image = Image.open("Lena.jpg")
draw = ImageDraw.Draw(image)
width = image.size[0]
height = image.size[1]
pix = image.load()
for i in range(width):
for j in range(height):
a = pix[i, j][0]
b = pix[i, j][1]
c = pix[i, j][2]
S = (a + b + c) // 3
draw.point((i, j), (S, S, S))
image.save("Lena_gray.jpg", "JPEG")
del draw
Ruby
class RGBColour
def to_grayscale
luminosity = Integer(0.2126*@red + 0.7152*@green + 0.0722*@blue)
self.class.new(luminosity, luminosity, luminosity)
end
end
class Pixmap
def to_grayscale
gray = self.class.new(@width, @height)
@width.times do |x|
@height.times do |y|
gray[x,y] = self[x,y].to_grayscale
end
end
gray
end
end
Scala
object BitmapOps
{
def luminosity(c:
Color)=(0.2126*c.getRed + 0.7152*c.getGreen + 0.0722*c.getBlue+0.5).toInt
def grayscale(bm:RgbBitmap)={
val image=new RgbBitmap(bm.width, bm.height)
for(x <- 0 until bm.width; y <- 0 until bm.height; l=luminosity(bm.getPixel(x,y)))
image.setPixel(x, y, new Color(l,l,l))
image
}
}
Visual Basic .NET
Dim pic As Bitmap = New Bitmap(PictureBox1.Image)
Dim x As Integer = pic.Width
Dim y As Integer = pic.Height
Dim gray = New Bitmap(pic.Width, pic.Height)
For x = 0 To (pic.Width) - 1
For y = 0 To (pic.Height) - 1
Dim c As Color = pic.GetPixel(x, y)
Dim r As Integer = c.R
Dim g As Integer = c.G
Dim b As Integer = c.B
Dim d As Integer = (r + g + b) \ 3
gray.SetPixel(x, y, Color.FromArgb(d, d, d))
Next
Next
PictureBox2.Image = gray
При написанні розділу використовувалися матеріали ресурсу https://rosettacode.org/wiki/Grayscale_image