画像のサンプル¶
1. スケッチ¶
コード
# include <Siv3D.hpp>
void Main()
{
// キャンバスのサイズ
constexpr Size CanvasSize{ 600, 600 };
// ペンの太さ
constexpr int32 PenThickness = 8;
// ペンの色
constexpr Color PenColor = Palette::Orange;
// 書き込み用の画像データを用意する
Image image{ CanvasSize, Palette::White };
// 表示用のテクスチャ(内容を更新するので DynamicTexture)
DynamicTexture texture{ image };
while (System::Update())
{
if (MouseL.pressed())
{
// 書き込む線の始点は直前のフレームのマウスカーソル座標
// (初回はタッチ操作時の座標のジャンプを防ぐため、現在のマウスカーソル座標にする)
const Point from = (MouseL.down() ? Cursor::Pos() : Cursor::PreviousPos());
// 書き込む線の終点は現在のマウスカーソル座標
const Point to = Cursor::Pos();
// image に線を書き込む
Line{ from, to }.overwrite(image, PenThickness, PenColor);
// 書き込み終わった image でテクスチャを更新
texture.fill(image);
}
// 描いたものを消去するボタンが押されたら
if (SimpleGUI::Button(U"Clear", Vec2{ 640, 40 }, 120))
{
// 画像を白で塗りつぶす
image.fill(Palette::White);
// 塗りつぶし終わった image でテクスチャを更新する
texture.fill(image);
}
// テクスチャを表示
texture.draw();
}
}
2. 万華鏡スケッチ¶
コード
# include <Siv3D.hpp>
void Main()
{
// キャンバスのサイズ
constexpr Size CanvasSize{ 600, 600 };
// 分割数
constexpr int32 N = 12;
// 背景色
constexpr Color BackgroundColor{ 20, 40, 60 };
// ウィンドウをキャンバスのサイズにする
Window::Resize(CanvasSize);
// 書き込み用の画像
Image image{ CanvasSize, BackgroundColor };
// 画像を表示するための動的テクスチャ
DynamicTexture texture{ image };
while (System::Update())
{
if (MouseL.pressed())
{
// 画面の中心が (0, 0) になるようにマウスカーソルの座標を移動させる
const Vec2 begin = ((MouseL.down() ? Cursor::PosF() : Cursor::PreviousPosF()) - CanvasSize / 2);
const Vec2 end = (Cursor::PosF() - CanvasSize / 2);
// 時間に応じて色を変化させる
const ColorF color = HSV{ (Scene::Time() * 60.0), 0.5, 1.0 };
for (int32 i = 0; i < N; ++i)
{
// 円座標に変換する
std::array<Circular, 2> cs = { begin, end };
for (auto& c : cs)
{
// 角度をずらす
if (IsEven(i))
{
c.theta = (-c.theta - 2_pi / N * (i - 1));
}
else
{
c.theta = (c.theta + 2_pi / N * i);
}
}
// ずらした位置をもとに、画像に線を書き込む
Line{ cs[0], cs[1] }.moveBy(CanvasSize / 2)
.overwrite(image, 2, color);
}
// 書き込んだ画像でテクスチャを更新する
texture.fillIfNotBusy(image);
}
if (MouseR.down()) // 右クリックでリセットする
{
// 画像を塗りつぶす
image.fill(BackgroundColor);
// 塗りつぶした画像でテクスチャを更新
texture.fill(image);
}
// テクスチャを描く
texture.draw();
}
}
3. ペイント¶
コード
# include <Siv3D.hpp>
void Main()
{
Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
// キャンバスのサイズ
constexpr Size CanvasSize{ 600, 600 };
// ペンの太さ
double penThickness = 8;
// ペンの色
HSV penColor = Palette::Orange;
// 書き込み用の画像データを用意する
Image image{ CanvasSize, Palette::White };
// 表示用のテクスチャ(内容を更新するので DynamicTexture)
DynamicTexture texture{ image };
const Array<String> modes = { U"Draw", U"Fill", U"Pick" };
size_t modeIndex = 0;
while (System::Update())
{
if (modeIndex == 0) // ペン
{
if (MouseL.pressed())
{
// 書き込む線の始点は直前のフレームのマウスカーソル座標
// (初回はタッチ操作時の座標のジャンプを防ぐため、現在のマウスカーソル座標にする)
const Point from = (MouseL.down() ? Cursor::Pos() : Cursor::PreviousPos());
// 書き込む線の終点は現在のマウスカーソル座標
const Point to = Cursor::Pos();
// image に線を書き込む
Line{ from, to }.overwrite(image, static_cast<int32>(penThickness), penColor, Antialiased::No);
// 書き込み終わった image でテクスチャを更新
texture.fill(image);
}
else if (MouseR.pressed())
{
const Point from = (MouseL.down() ? Cursor::Pos() : Cursor::PreviousPos());
const Point to = Cursor::Pos();
Line{ from, to }.overwrite(image, static_cast<int32>(penThickness), Palette::White, Antialiased::No);
texture.fill(image);
}
}
else if (modeIndex == 1) // 塗りつぶし
{
if (MouseL.down())
{
image.floodFill(Cursor::Pos(), penColor);
texture.fill(image);
}
else if (MouseR.down())
{
image.floodFill(Cursor::Pos(), Palette::White);
texture.fill(image);
}
}
else // ピッカー
{
if (MouseL.down())
{
const Point cursorPos = Cursor::Pos();
if (InRange(cursorPos.x, 0, (image.width() - 1))
&& InRange(cursorPos.y, 0, (image.height() - 1)))
{
penColor = image[cursorPos];
}
}
}
if (SimpleGUI::Button(U"Save", Vec2{ 620, 40 }, 160))
{
image.saveWithDialog();
}
// 描いたものを消去するボタンが押されたら
if (SimpleGUI::Button(U"Clear", Vec2{ 620, 100 }, 160))
{
// 画像を白で塗りつぶす
image.fill(Palette::White);
// 塗りつぶし終わった image でテクスチャを更新する
texture.fill(image);
}
// 色の選択
SimpleGUI::ColorPicker(penColor, Vec2{ 620, 160 });
// ペンの太さ
SimpleGUI::Slider(penThickness, 1.0, 30.0, Vec2{ 620, 300 }, 160);
// モードの選択
SimpleGUI::RadioButtons(modeIndex, modes, Vec2{ 620, 360 });
// テクスチャを表示
texture.draw();
}
}
4. Image to Polygon¶
コード
# include <Siv3D.hpp>
void Main()
{
// 使用する画像
const Image image{ U"example/siv3d-kun.png" };
// テクスチャの表示位置
constexpr Vec2 BasePos{ 40, 80 };
// テクスチャ
const Texture texture{ image };
// アルファ値 1 以上の領域を Polygon 化する
const Polygon polygon = image.alphaToPolygon(1, AllowHoles::No);
// Polygon 単純化の許容距離(ピクセル)
double maxDistance = 4.0;
// 単純化した Polygon
Polygon simplifiedPolygon = polygon.simplified(maxDistance);
while (System::Update())
{
// 単純化した Polygon の三角形数を表示する
ClearPrint();
Print << U"{} triangles"_fmt(simplifiedPolygon.num_triangles());
texture.draw(BasePos);
// 単純化した Polygon をテクスチャ上に表示する
simplifiedPolygon.movedBy(BasePos)
.draw(ColorF{ 1.0, 1.0, 0.0, 0.2 })
.drawWireframe(2, Palette::Yellow);
// 単純化した Polygon をテクスチャの横に表示する
simplifiedPolygon.movedBy(BasePos.movedBy(320, 0))
.draw(ColorF{ 0.5 });
// Polygon 単純化の許容距離を設定するスライダー
if (SimpleGUI::Slider(U"{:.1f}"_fmt(maxDistance), maxDistance, 0, 50, Vec2{ 400, 40 }, 60, 240))
{
// スライダーに変更があれば、単純化した Polygon を新しい許容距離で再作成
simplifiedPolygon = polygon.simplified(maxDistance);
}
}
}
5. JPEG Glitch¶
コード
# include <Siv3D.hpp>
void Main()
{
Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
// 画像
const Image image{ U"example/windmill.png" };
// 表示用の動的テクスチャ
DynamicTexture texture{ image };
// JPEG のバイナリデータ
const Blob originalBlob = image.encodeJPEG();
// 改変するデータの個数
const size_t noiseCount = (image.num_pixels() / 4000);
while (System::Update())
{
if (SimpleGUI::Button(U"Glitch", Vec2{ 40, 40 }))
{
// Array を作成
Blob modifiedBlob = originalBlob;
for (size_t i = 0; i < noiseCount; ++i)
{
// ランダムな位置の 1 バイトについて、ランダムな値に書き換える。
// ヘッダ部分(先頭)は改変しない。
const size_t index = Random<size_t>(630, (modifiedBlob.size() - 1));
modifiedBlob[index] = Byte{ RandomUint8() };
}
// JPEG データとして読み込んで画像を作成、動的テクスチャに転送
texture.fill(Image{ MemoryReader{ modifiedBlob }, ImageFormat::JPEG });
}
texture.drawAt(Scene::Center());
}
}
6. 模写アプリ¶
コード
# include <Siv3D.hpp>
// 2 つの画像の距離を計算する関数
double Diff(const Image& a, const Image& b)
{
const Color* pA = a.data();
const Color* pB = b.data();
const Color* const pAEnd = (pA + a.num_pixels());
double d = 0.0;
// すべてのピクセルに対して
while (pA != pAEnd)
{
d += (AbsDiff(pA->r, pB->r) + AbsDiff(pA->g, pB->g) + AbsDiff(pA->b, pB->b));
++pA;
++pB;
}
return d;
}
void Main()
{
Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
// 目標とする画像をファイルダイアログで選択、シーンのサイズにフィットするようリサイズ
const Image target = Dialog::OpenImage().fit(Scene::Size());
// 現在の画像
Image image{ target.size(), Palette::White };
// 直前の画像
Image old = image;
// 現在の画像を表示するための動的テクスチャ
DynamicTexture texture{ image };
// 目標との距離
double d1 = Diff(target, image);
while (System::Update())
{
for (int32 i = 0; i < 100; ++i)
{
// ランダムな座標
const Point pos = RandomPoint(Rect{ image.size() });
// ランダムな色
const ColorF color{ Random(), Random(), Random(), Random() };
// ランダムな半径
const int32 size = Random(1, 10);
// 円を現在の画像に書き込む
Circle{ pos, size }.paint(image, color);
// 目標との距離を計算
const double d2 = Diff(target, image);
if (d2 < d1) // 目標に近づいていたら採用
{
d1 = d2;
old = image;
}
else // 近づいていなかったら元に戻す
{
image = old;
}
}
// 動的テクスチャを更新する
texture.fill(image);
// テクスチャを画面の中心に描画する
texture.drawAt(Scene::Center());
// 保存ボタン
if (SimpleGUI::Button(U"Save", Vec2{ 660, 550 }))
{
// 現在の画像をファイルダイアログ経由で保存する
image.saveWithDialog();
}
}
}
7. GrabCut による背景分離と Inpaint による修復¶
コード
# include <Siv3D.hpp>
void Main()
{
Window::Resize(1280, 720);
Scene::SetBackground(ColorF{ 0.8, 1.0, 0.9 });
const Image image = Dialog::OpenImage().fit(Size{ 480, 320 });
const Texture texture{ image };
GrabCut grabcut{ image };
Image mask{ image.size(), Color{0, 0} };
Image background{ image.size(), Palette::Black };
Image foreground{ image.size(), Palette::Black };
Image inpaint;
DynamicTexture maskTexture{ mask };
Grid<GrabCutClass> result;
DynamicTexture classTexture;
DynamicTexture backgroundTexture{ background };
DynamicTexture foregroundTexture{ foreground };
DynamicTexture inpaintTexture{ foreground };
constexpr Color BackgroundColor{ 0, 0, 255 };
constexpr Color ForegroundColor{ 250, 100, 50 };
while (System::Update())
{
if ((not classTexture) || MouseL.up() || MouseR.up())
{
grabcut.update(mask, ForegroundColor, BackgroundColor);
grabcut.getResult(result);
classTexture.fill(Image(result, [](GrabCutClass c) { return Color(80 * FromEnum(c)); }));
for (auto p : step(image.size()))
{
const bool isBackground = (GrabCutClass::PossibleBackground <= result[p]);
if (isBackground)
{
background[p] = image[p];
foreground[p] = Color{ 0,0 };
}
else
{
foreground[p] = image[p];
background[p] = Color{ 0,0 };
}
}
ImageProcessing::Inpaint(background, background, Color{ 0, 0 }, inpaint);
inpaint.gaussianBlur(3);
foregroundTexture.fill(foreground);
backgroundTexture.fill(background);
inpaintTexture.fill(inpaint);
}
if (MouseL.pressed())
{
const Point from = MouseL.down() ? Cursor::Pos() : Cursor::PreviousPos();
const Point to = Cursor::Pos();
Line{ from, to }.overwrite(mask, 4, ForegroundColor, Antialiased::No);
maskTexture.fill(mask);
}
else if (MouseR.pressed())
{
const Point from = MouseR.down() ? Cursor::Pos() : Cursor::PreviousPos();
const Point to = Cursor::Pos();
Line{ from, to }.overwrite(mask, 4, BackgroundColor, Antialiased::No);
maskTexture.fill(mask);
}
texture.draw();
maskTexture.draw();
classTexture.draw(600, 0);
backgroundTexture.scaled(0.7).regionAt(200, 520).draw(ColorF{ 0 });
backgroundTexture.scaled(0.7).drawAt(200, 520);
foregroundTexture.scaled(0.7).regionAt(1080, 520).draw(ColorF{ 0 });
foregroundTexture.scaled(0.7).drawAt(1080, 520);
inpaintTexture.drawAt(640, 520);
{
const Transformer2D transformer{ Mat3x2::Scale(1.1, Vec2{640, 520}.movedBy(0, image.height() / 2)).translated((Scene::Center() - Cursor::Pos()) * 0.04) };
foregroundTexture.drawAt(640, 520);
}
}
}
8. ドロップされたイラストから顔を検出¶
コード
# include <Siv3D.hpp>
void Main()
{
Texture texture;
double scale = 1.0;
// 検出器。正面を向いた顔の学習データを使用して分類
const CascadeClassifier animeFaceDetector{ U"example/objdetect/haarcascade/face_anime.xml" };
Array<Rect> detectedFaces;
while (System::Update())
{
// ファイルがドロップされた
if (DragDrop::HasNewFilePaths())
{
// ファイルを画像として読み込めた
if (const Image image{ DragDrop::GetDroppedFilePaths().front().path })
{
// イラスト内の顔を検出する
detectedFaces = animeFaceDetector.detectObjects(image);
// 画面のサイズに合うように画像を拡大縮小
texture = Texture{ image.fitted(Scene::Size()) };
// 画像の拡大縮小率
scale = (static_cast<double>(texture.width()) / image.width());
}
}
if (texture)
{
texture.draw(0, 0);
// 顔の領域の座標を表示に合わせる
const Transformer2D transformer{ Mat3x2::Scale(scale) };
for (const auto& detectedFace : detectedFaces)
{
detectedFace.drawFrame((4 / scale), ColorF{ 1.0, 0.0, 0.0, Periodic::Sine0_1(1.5s) });
}
}
}
}
9. マンデルブロ集合¶
コード
# include <Siv3D.hpp>
int32 Mandelbrot(double x, double y)
{
double a = 0.0, b = 0.0;
for (int32 n = 0; n < 360; ++n)
{
const double t = (a * a - b * b + x);
const double u = (2.0 * a * b + y);
if (4.0 < (t * t + u * u))
{
return n;
}
a = t;
b = u;
}
return 0;
}
void Main()
{
constexpr Size SceneSize{ 640, 480 };
Window::Resize(SceneSize);
Vec2 center(0, 0);
double scale = -4.0;
// 結果を保存する画像
Image image{ SceneSize, Palette::Black };
// 描画用の動的テクスチャ
DynamicTexture texture(image);
while (System::Update())
{
const double wheel = Mouse::Wheel();
const bool clicked = (MouseL | MouseR).down();
// 最初のフレームか、操作があったときだけ更新する
if (wheel || clicked || (Scene::FrameCount() == 1))
{
scale -= wheel;
const double s = Pow(1.25, scale);
const double d = ((1.0 / s) / SceneSize.x);
if (clicked)
{
center += (Cursor::PosF() - SceneSize / 2) * d;
}
const double xb = (center.x - d * (SceneSize.x * 0.5));
const double yb = (center.y - d * (SceneSize.y * 0.5));
for (int32 y = 0; y < SceneSize.y; ++y)
{
const double yPos = yb + (d * y);
for (int32 x = 0; x < SceneSize.x; ++x)
{
const double xPos = xb + (d * x);
if (const int32 m = Mandelbrot(xPos, yPos))
{
image[y][x] = HSV{ (240 - m), 0.8, 1.0 };
}
else
{
image[y][x] = Palette::Black;
}
}
}
// 動的テクスチャの中身を image で更新する
texture.fill(image);
}
// テクスチャを描画する
texture.draw();
}
}