Omówienie różnych sposobów wyszarzania obrazu:
http://www.tannerhelland.com/3643/grayscale-image-algorithm-vb6/
Omówienie różnych sposobów wyszarzania obrazu:
http://www.tannerhelland.com/3643/grayscale-image-algorithm-vb6/
Azarien napisał(a):
Jeśli wydajność jest istotna (skoro to mają być bitmapy „hurtowo”) to obróbkę grafiki bym zrobił w OpenGL-u na GPU.
Chyba mi się za bardzo nudziło, bo popełniłem taki program (konsolowy) który wczytuje plik .png, robi szaro za pomocą OpenGL-a i zapisuje do png.
// helper.h
#pragma once
struct bitmap_t
{
int width, height;
unsigned char *data;
};
struct bitmap_t load_bitmap_from_file(const wchar_t *fileName);
void save_bitmap_to_file(struct bitmap_t bitmap, const wchar_t *fileName);
struct bitmap_t new_bitmap(int width, int height);
void free_bitmap(struct bitmap_t bitmap);
Jednak napisanie tych funkcji pozostawiamy jako zadanie czytelnikowi ;-)
Właściwy program:
#include <assert.h>
#include <Windows.h>
#include <shlwapi.h>
#include <wincodec.h>
#include <gl/glew.h>
#include "helper.h"
#pragma comment(lib, "opengl32.lib")
void setup_window()
{
WNDCLASS wc = {
.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC,
.lpfnWndProc = DefWindowProc,
.lpszClassName = L"szary",
.hInstance = GetModuleHandle(NULL)
};
ATOM atom = RegisterClass(&wc);
HWND hwnd = CreateWindow(
MAKEINTATOM(atom),
L"Szary",
WS_POPUP,
CW_USEDEFAULT, CW_USEDEFAULT,
320, 240,
(HWND)NULL, (HMENU)NULL, wc.hInstance, (LPVOID)NULL
);
PIXELFORMATDESCRIPTOR pfd = {
.nSize = sizeof(pfd),
.nVersion = 1,
.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL,
.cColorBits = 32
};
HDC dc = GetDC(hwnd);
SetPixelFormat(dc, ChoosePixelFormat(dc, &pfd), NULL);
HGLRC rc = wglCreateContext(dc);
assert(rc);
wglMakeCurrent(dc, rc);
glewInit();
}
void setup_framebuffer(int width, int height)
{
GLuint renderbuffer;
glGenRenderbuffersEXT(1, &renderbuffer);
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, renderbuffer);
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_RGBA8, width, height);
GLuint framebuffer;
glGenFramebuffersEXT(1, &framebuffer);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, renderbuffer);
GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
assert(status == GL_FRAMEBUFFER_COMPLETE_EXT);
}
const char *fs_source =
"#version 120\n "
"uniform sampler2D u_tex; "
"void main() "
"{ "
" vec4 texel = texture2D(u_tex, gl_TexCoord[0].st); "
" float gray = texel.r * 0.299 + texel.g * 0.587 + texel.b * 0.114; "
" gl_FragColor = vec4(gray, gray, gray, texel.a); "
"} ";
void setup_shader()
{
GLuint shader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(shader, 1, &fs_source, NULL);
glCompileShader(shader);
GLint status;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
assert(status);
GLuint program = glCreateProgram();
glAttachShader(program, shader);
glLinkProgram(program);
glUseProgram(program);
GLint u_tex = glGetUniformLocation(program, "u_tex");
glUniform1i(u_tex, 0);
}
void setup_transform(int width, int height)
{
float matrix[] = { 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, -1, -1, 0, 1 };
matrix[0] /= width;
matrix[5] /= height;
glLoadMatrixf(matrix);
glViewport(0, 0, width, height);
}
void draw_quad(int width, int height)
{
glBegin(GL_QUADS);
glTexCoord2f(0, 0); glVertex2f(0, 0);
glTexCoord2f(1, 0); glVertex2f(width, 0);
glTexCoord2f(1, 1); glVertex2f(width, height);
glTexCoord2f(0, 1); glVertex2f(0, height);
glEnd();
glFinish();
}
void setup_texture(struct bitmap_t bitmap)
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, bitmap.width, bitmap.height, 0, GL_BGRA, GL_UNSIGNED_BYTE, bitmap.data);
}
int main()
{
CoInitialize(NULL);
struct bitmap_t srcBitmap = load_bitmap_from_file(L"image.png");
setup_window();
setup_framebuffer(srcBitmap.width, srcBitmap.height);
setup_shader();
setup_transform(srcBitmap.width, srcBitmap.height);
setup_texture(srcBitmap);
draw_quad(srcBitmap.width, srcBitmap.height);
struct bitmap_t dstBitmap = new_bitmap(srcBitmap.width, srcBitmap.height);
glReadPixels(0, 0, dstBitmap.width, dstBitmap.height, GL_BGRA, GL_UNSIGNED_BYTE, dstBitmap.data);
save_bitmap_to_file(dstBitmap, L"image_gray.png");
free_bitmap(srcBitmap);
free_bitmap(dstBitmap);
CoUninitialize();
return 0;
}
Program napisany pod Visualem 2017. Z zewnętrznych bibliotek używa jedynie GLEW (paczka z NuGeta unofficial-flayan-glew) ale dałoby się jej uniknąć.
Laptop na którym to pisałem ma OpenGL 2.1, więc pod taką wersję kod jest pisany.
W niektórych miejscach poszedłem na łatwiznę. Reklamacje że to czy tamto jest „deprecated” nie będą uwzględniane ;-)
Program nie otwiera żadnego (widocznego) okienka. Funkcja setup_window() tworzy okno bo musi, ale go nie pokazuje.
load_bitmap_from_file()
i save_bitmap_to_file()
nie podałem żeby nie wydłużać posta), ustawia wyszarzający fragment shader, framebuffer w pamięci (poza ekranem), transformację "pixel exact" (współrzędne modelu dokładnie odpowiadają pikselom framebuffera), rysuje prostokąt i zapisuje wynik do pliku.
@cerrato: podejrzewam że przy tak prostym efekcie odczyt i zapis pliku będzie trwał dłużej niż sam algorytm czy to na GPU czy na CPU…