Програмний код

Робота з комп'ютерною графікою спирається на програмування графічних ефектів. При вивченні даного курсу ми не використовуємо якусь конкретну мову програмування, але все ж, потрібно на чомусь програмувати.
У даному додатку містяться приклади коду на різних мовах, які реалізують одну й ту ж задачу - перетворення кольорового зображення в градації сірого (неправильно кажучи - в чорно-біле).
А вже відштовхуючись від відомого коду, нескладно реалізувати той чи інший графічний ефект.
Для перетворення зображення з повнокольорового у градації сірого, існує багато різних формул. Найпростіша являє собою середнє арифметичне базових кольорів: \[ 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