Skip to content

Instantly share code, notes, and snippets.

@oyakodon
Last active May 23, 2020 08:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save oyakodon/c7a72be0d5f4b29c4a5deff5914d60e8 to your computer and use it in GitHub Desktop.
Save oyakodon/c7a72be0d5f4b29c4a5deff5914d60e8 to your computer and use it in GitHub Desktop.
Picture Brush Paint

Picture Brush Paint

capture.gif

特徴

  • 画像をブラシにして描画できるペイントアプリケーション
  • 描画する画像の大きさ(scale), 画像と画像の間隔(gap)が変更可能
  • png, jpg, gif画像を読み込むことができ、さらにgif動画の場合は、動く

参考

#include <Siv3D.hpp> // OpenSiv3D v0.4.3
struct AnimationImage
{
Array<Image> images;
Array<Texture> textures_prev;
// フレームの時間
Array<int32> delays;
int32 duration = 0;
explicit operator bool() const noexcept
{
return !images.isEmpty();
}
Size size() const noexcept
{
if (!images)
{
return Size(0, 0);
}
return images.front().size();
}
size_t frames() const noexcept
{
return images.size();
}
size_t getFrameIndex(int32 timeMillisec) const noexcept
{
return AnimatedGIFReader::MillisecToIndex(timeMillisec, delays, duration);
}
const Image& getImage(int32 timeMillisec) const noexcept
{
return images[getFrameIndex(timeMillisec)];
}
const Texture& getPreviewTexture(int32 timeMillisec) const noexcept
{
return textures_prev[getFrameIndex(timeMillisec)];
}
};
void Main()
{
Window::SetTitle(U"Picture Brush Paint");
DragDrop::AcceptFilePaths(true);
const Font font(24);
Image original;
Texture previewTex;
{
Image prev(100, 100, Palette::Gray);
previewTex = Texture(prev);
}
Image brushPic;
constexpr Size canvasSize(600, 600);
Image image(canvasSize, Palette::White);
DynamicTexture tex(image);
Point from = Point(0, 0);
Point to = Point(0, 0);
double gap = 0.4 / 0.9;
double scale = 1.0;
Optional<AnimationImage> animation;
while (System::Update())
{
// Animation
const int32 timeMillisec = static_cast<int32>(Scene::Time() * 1000);
if (animation)
{
brushPic = animation->getImage(timeMillisec).scaled(scale);
}
// File Dropped
if (DragDrop::HasNewFilePaths())
{
String path = DragDrop::GetDroppedFilePaths()[0].path;
String ext = FileSystem::Extension(path);
animation = none;
original = Image();
if (ext == U"gif")
{
// GIF ファイルを開く
const AnimatedGIFReader gif(path);
if (gif)
{
Array<Image> images;
animation = AnimationImage();
// GIF アニメーションを読み込み
if (gif.read(images, animation->delays, animation->duration))
{
animation->images = Array<Image>(images);
double s = images.front().width() > images.front().height() ? 100.0 / images.front().width() : 100.0 / images.front().height();
Point p = { (int)(50 - images.front().width() * s / 2.0), (int)(50 - images.front().height() * s / 2.0) };
animation->textures_prev = images.map([s, p](const Image& i)
{
Image prev(100, 100, Palette::Gray);
i.scaled(s).paint(prev, p);
return Texture(prev);
});
}
else
{
animation = none;
}
}
}
if (ext == U"png" || ext == U"jpeg" || ext == U"jpg" || (!animation && ext == U"gif"))
{
original = Image(path);
brushPic = original.scaled(scale);
Image prev(100, 100, Palette::Gray);
double s = original.width() > original.height() ? 100.0 / original.width() : 100.0 / original.height();
Point p = { (int)(50 - original.width() * s / 2.0), (int)(50 - original.height() * s / 2.0) };
original.scaled(s).paint(prev, p);
previewTex = Texture(prev);
}
}
// MouseL pressed
if (MouseL.down())
{
from = Cursor::Pos();
if (animation || original)
{
brushPic.paint(image, from.movedBy(-brushPic.size() / 2));
tex.fill(image);
}
}
// MouseL pressing
if (MouseL.pressed())
{
to = Cursor::Pos();
if (animation || original)
{
double dist = from.distanceFrom(to);
double l = Min(brushPic.width(), brushPic.height()) * (0.9 * gap + 0.1);
if (dist > l)
{
brushPic.paint(image, to.movedBy(-brushPic.size() / 2));
from = to;
tex.fill(image);
}
}
}
// Clear
if (SimpleGUI::Button(U"Clear", Vec2(640, 20), 120))
{
image.fill(Palette::White);
tex.fill(image);
}
// Save
if (SimpleGUI::Button(U"Save", Vec2(640, 70), 120))
{
auto path = Dialog::SaveImage();
if (path)
{
image.savePNG(path.value());
}
}
// Gap
font(U"gap:").draw(Vec2(640, 125));
font((0.9 * gap + 0.1)).draw(Vec2(640, 155));
SimpleGUI::Slider(gap, Vec2(640, 190), 120);
// Scale
font(U"scale:").draw(Vec2(640, 240));
font((0.9 * scale + 0.1)).draw(Vec2(640, 265));
if (SimpleGUI::Slider(scale, Vec2(640, 300), 120))
{
if (original)
{
brushPic = original.scaled(scale);
}
}
// Brush preview
font(U"brush:").draw(Vec2(640, 380));
if (animation)
{
Rect(650, 420, 100, 100)(animation->getPreviewTexture(timeMillisec)).draw();
}
else
{
Rect(650, 420, 100, 100)(previewTex).draw();
}
tex.draw();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment