cnjinhao / nana

a modern C++ GUI library
https://nana.acemind.cn
Boost Software License 1.0
2.32k stars 333 forks source link

Suggestion to implement image rotation interface #540

Open heartsg opened 4 years ago

heartsg commented 4 years ago

Rotation is almost a defecto implementation for any simple game engine. For example, for python pygame, you can use pygame.transform.rotate to rotate an image.

nana has a beautiful interface which is quite attractive as an alternative framework to teach children programming skills by games (the others include python & scratch). It's probably better to extend the paint & graphics functionality a little bit.

I did a simple experiment according to (which uses HDC and HBITMAP), https://www.codeguru.com/cpp/g-m/gdi/article.php/c3693/Rotate-a-Bitmap-at-Any-Angle-Without-GetPixelSetPixel.htm,

Using the current interface provided (I am not sure whether this is the best way or not), it seems too troublesome.

// load image
paint::image player("resources/BB/images/dude.png");
// copy to graphics to get HBITMAP & HDC
paint::graphics playerg(size(player.size().width, player.size().height));
player.paste(playerg, point(0, 0));

// create a rotated graphics
paint::graphics player_rotate(size(playerg.size().width * 1.5, playerg.size().height * 1.5));
int width, height;
// rotate
RotateMemoryDC((HBITMAP)playerg.pixmap(), (HDC)playerg.context(), playerg.size().width, 
         playerg.size().height, playerangle, (HDC)player_rotate.context(), width, height);
// create a pixel buffer (for alpha_channel only)
paint::pixel_buffer player_buffer(player_rotate.handle(), rectangle(0, 0, width, height));
player_buffer.alpha_channel(true);
// paste to drawing
player_buffer.paste(graph.handle(), playerpos);
beru commented 4 years ago

I'm interested in this topic since I also do graphics programming just like many other programmers around the globe.

As of now (v1.7.4), nana's graphics implementation is written in files inside source/paint folder. By looking at graphics.cpp, we would instantaneously realize that nana uses graphics device interface (GDI) on Windows, and Xlib on other platforms.

The functionalities that GDI and Xlib offer are somewhat primitive, as they were implementated around decades ago. They are very popular and surely not deprecated yet but seems pretty outdated. The good thing about them is they are widely supported, also not so difficult to use.

I suppose professional game developers use popular game engines like Unreal Engine or Unity. Those game engines's developers know quite a lot about modern 3D computer graphics, DirectX, OpenGL, Vulkan and so on. So anyone who seriously want to code cutting-edge staffs, they should never rely on nana's graphics routines because nana's graphics routines are primarily purposed to render gui widgets.

Aside from hardcore professionalism, there are a lot of alternative ways to draw images on computer screen. Personally, I still use GDI on Windows and recently (around an year-ish ago?) started using Direct2D. And just today, I tried Skia.

Here's the sample code to use Skia.

Example code ```cpp #pragma comment(lib, "skia.lib") #include "SkCanvas.h" #include "SkRefCnt.h" #include "SkData.h" #include "SkImage.h" #include "SkStream.h" #include "SkSurface.h" #include "SkPath.h" #include "SkPaint.h" #include "include/codec/SkCodec.h" void draw(SkCanvas* can, const SkBitmap& bmp) { const SkScalar scale = 256.0f; const SkScalar R = 0.45f * scale; const SkScalar TAU = 6.2831853f; SkPath path; for (int i = 0; i < 5; ++i) { SkScalar theta = 2 * i * TAU / 5; if (i == 0) { path.moveTo(R * cos(theta), R * sin(theta)); } else { path.lineTo(R * cos(theta), R * sin(theta)); } } path.close(); SkPaint p; p.setAntiAlias(true); can->clear(SK_ColorWHITE); can->translate(0.5f * scale, 0.5f * scale); can->drawPath(path, p); can->rotate(20.0); can->drawBitmap(bmp, 0.0, 0.0); } void raster(int width, int height, const char* srcPath, const char* dstPath) { auto codec = SkCodec::MakeFromStream(SkFILEStream::Make(srcPath)); SkBitmap bmp; bmp.allocPixels(codec->getInfo()); codec->getPixels(bmp.info(), bmp.getPixels(), bmp.rowBytes()); sk_sp rasterSurface = SkSurface::MakeRasterN32Premul(width, height); SkCanvas* rasterCanvas = rasterSurface->getCanvas(); draw(rasterCanvas, bmp); #if 1 sk_sp img(rasterSurface->makeImageSnapshot()); if (!img) { return; } sk_sp png(img->encodeToData()); if (!png) { return; } SkFILEWStream out(dstPath); (void)out.write(png->data(), png->size()); #else sk_sp png(SkImage::MakeFromBitmap(bmp)->encodeToData(SkEncodedImageFormat::kPNG, 100)); if (!png) { return; } SkFILEWStream out(dstPath); (void)out.write(png->data(), png->size()); #endif } int main(int argc, char* argv[]) { raster(1280, 960, "src.png", "dst.png"); return 0; } ```

I've thought building the library would be time consuming so I obtained skia.lib via https://github.com/rust-skia/skia-binaries

There are advantages and disadvantages in every decisions. For example, if nana would use Skia library, its binary size will increase for sure.

ErrorFlynn commented 4 years ago

I just found out that paint::pixel_buffer has a limited ability to rotate an image. It can only rotate counterclockwise, without clipping.

paint::image img {"path/to/image"};
paint::graphics g_img {img.size()};
img.paste(g_img, {0, 0});
paint::pixel_buffer pixbuf {g_img.handle(), rectangle{img.size()}};
paint::pixel_buffer rotated {pixbuf.rotate(45, colors::white)}; // 45 degrees angle