戻る
AIに画像バッファを作ってもらう (2025/02/22)
「プログラミングをAIに任せるとか、全然興味ないんだよね」という話をしたら「まだそんな素朴な事言ってるの?(意訳)」という反応を貰った。「ま…まああんたほどの実力者がそういうのなら………」というわけで、AIにプログラミングして貰おう。
昔ながらのグラフィックスプログラマには欠かせない、
画像バッファを作って貰います。AI環境はよく分からないので、Windows標準のCopilot に任せてみます。
A,R,G,B 各8bit のピクセルをもつ、C++の画像バッファクラスを作ってください
もちろん、お任せください!ここに、A, R, G, B 各 8 ビットのピクセルを持つ C++ の画像バッファクラスの簡単な例を示します。
#include <iostream>
#include <vector>
class ImageBuffer {
public:
ImageBuffer(int width, int height)
: width(width), height(height), buffer(width * height * 4, 0) {}
void setPixel(int x, int y, uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) {
if (x >= 0 && x < width && y >= 0 && y < height) {
int index = (y * width + x) * 4;
buffer[index] = red;
buffer[index + 1] = green;
buffer[index + 2] = blue;
buffer[index + 3] = alpha;
} else {
std::cerr << "Pixel coordinates out of bounds" << std::endl;
}
}
void getPixel(int x, int y, uint8_t& red, uint8_t& green, uint8_t& blue, uint8_t& alpha) const {
if (x >= 0 && x < width && y >= 0 && y < height) {
int index = (y * width + x) * 4;
red = buffer[index];
green = buffer[index + 1];
blue = buffer[index + 2];
alpha = buffer[index + 3];
} else {
std::cerr << "Pixel coordinates out of bounds" << std::endl;
}
}
int getWidth() const { return width; }
int getHeight() const { return height; }
private:
int width, height;
std::vector buffer;
};
int main() {
ImageBuffer img(3, 3);
img.setPixel(1, 1, 255, 0, 0, 255); // 赤色のピクセルをセット
uint8_t r, g, b, a;
img.getPixel(1, 1, r, g, b, a);
std::cout << "Red: " << (int)r << ", Green: " << (int)g << ", Blue: " << (int)b << ", Alpha: " << (int)a << std::endl;
return 0;
}
このクラス ImageBuffer は幅と高さを指定して初期化され、各ピクセルは RGBA 形式の 8 ビット値を持ちます。setPixel メソッドは特定の位置にピクセルの色を設定し、getPixel メソッドはピクセルの色を取得します。
いかがでしょうか?もし何か特定の機能や改善点がありましたら、お知らせください!
ImageBuffer に、矩形を塗りつぶすメソッドを追加してください。
もちろんです!ImageBuffer クラスに矩形を塗りつぶすメソッド fillRectangle を追加してみましょう。以下にその実装例を示します。
#include <iostream>
#include <vector>
class ImageBuffer {
public:
ImageBuffer(int width, int height)
: width(width), height(height), buffer(width * height * 4, 0) {}
void setPixel(int x, int y, uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) {
if (x >= 0 && x < width && y >= 0 && y < height) {
int index = (y * width + x) * 4;
buffer[index] = red;
buffer[index + 1] = green;
buffer[index + 2] = blue;
buffer[index + 3] = alpha;
} else {
std::cerr << "Pixel coordinates out of bounds" << std::endl;
}
}
void getPixel(int x, int y, uint8_t& red, uint8_t& green, uint8_t& blue, uint8_t& alpha) const {
if (x >= 0 && x < width && y >= 0 && y < height) {
int index = (y * width + x) * 4;
red = buffer[index];
green = buffer[index + 1];
blue = buffer[index + 2];
alpha = buffer[index + 3];
} else {
std::cerr << "Pixel coordinates out of bounds" << std::endl;
}
}
void fillRectangle(int x, int y, int rectWidth, int rectHeight, uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) {
for (int i = x; i < x + rectWidth; ++i) {
for (int j = y; j < y + rectHeight; ++j) {
setPixel(i, j, red, green, blue, alpha);
}
}
}
int getWidth() const { return width; }
int getHeight() const { return height; }
private:
int width, height;
std::vector buffer;
};
int main() {
ImageBuffer img(5, 5);
img.fillRectangle(1, 1, 3, 3, 0, 255, 0, 255); // 緑色で矩形を塗りつぶす
for (int y = 0; y < img.getHeight(); ++y) {
for (int x = 0; x < img.getWidth(); ++x) {
uint8_t r, g, b, a;
img.getPixel(x, y, r, g, b, a);
std::cout << "(" << (int)r << "," << (int)g << "," << (int)b << "," << (int)a << ") ";
}
std::cout << std::endl;
}
return 0;
}
この fillRectangle メソッドは、指定された座標 (x, y) から始まる幅と高さの矩形を、指定された RGBA 値で塗りつぶします。例では、5x5 の画像の (1, 1) から (3, 3) の範囲を緑色で塗りつぶしています。
追加の機能や別の例が必要でしたら、どうぞお知らせください!
fillRectangle が遅いので、ポインタを使って最適化してください。
了解しました。ポインタを使って fillRectangle メソッドを最適化しましょう。以下に、その実装例を示します。
#include <iostream>
#include <vector>
class ImageBuffer {
public:
ImageBuffer(int width, int height)
: width(width), height(height), buffer(new uint8_t[width * height * 4]()) {}
~ImageBuffer() {
delete[] buffer;
}
void setPixel(int x, int y, uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) {
if (x >= 0 && x < width && y >= 0 && y < height) {
uint8_t* pixel = buffer + (y * width + x) * 4;
pixel[0] = red;
pixel[1] = green;
pixel[2] = blue;
pixel[3] = alpha;
} else {
std::cerr << "Pixel coordinates out of bounds" << std::endl;
}
}
void getPixel(int x, int y, uint8_t& red, uint8_t& green, uint8_t& blue, uint8_t& alpha) const {
if (x >= 0 && x < width && y >= 0 && y < height) {
const uint8_t* pixel = buffer + (y * width + x) * 4;
red = pixel[0];
green = pixel[1];
blue = pixel[2];
alpha = pixel[3];
} else {
std::cerr << "Pixel coordinates out of bounds" << std::endl;
}
}
void fillRectangle(int x, int y, int rectWidth, int rectHeight, uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) {
for (int j = y; j < y + rectHeight; ++j) {
uint8_t* row = buffer + (j * width + x) * 4;
for (int i = 0; i < rectWidth; ++i) {
row[0] = red;
row[1] = green;
row[2] = blue;
row[3] = alpha;
row += 4;
}
}
}
int getWidth() const { return width; }
int getHeight() const { return height; }
private:
int width, height;
uint8_t* buffer;
};
int main() {
ImageBuffer img(5, 5);
img.fillRectangle(1, 1, 3, 3, 0, 255, 0, 255); // 緑色で矩形を塗りつぶす
for (int y = 0; y < img.getHeight(); ++y) {
for (int x = 0; x < img.getWidth(); ++x) {
uint8_t r, g, b, a;
img.getPixel(x, y, r, g, b, a);
std::cout << "(" << (int)r << "," << (int)g << "," << (int)b << "," << (int)a << ") ";
}
std::cout << std::endl;
}
return 0;
}
この実装では、バッファとして動的に確保された配列 (new uint8_t[]) を使用し、fillRectangle メソッド内でポインタを使ってピクセルデータに直接アクセスしています。これにより、ループ内でのインデックス計算が減り、パフォーマンスが向上します。
他に追加したい機能や改善点があれば教えてください!
fillRectangle は範囲外を指定するとクラッシュするので、カリングやクリッピング機能を追加してください。
わかりました。それでは、塗りつぶしの際に範囲外の指定を避けるために、カリングやクリッピング機能を追加します。以下にその実装を示します。
#include <iostream>
#include <vector>
class ImageBuffer {
public:
ImageBuffer(int width, int height)
: width(width), height(height), buffer(new uint8_t[width * height * 4]()) {}
~ImageBuffer() {
delete[] buffer;
}
void setPixel(int x, int y, uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) {
if (x >= 0 && x < width && y >= 0 && y < height) {
uint8_t* pixel = buffer + (y * width + x) * 4;
pixel[0] = red;
pixel[1] = green;
pixel[2] = blue;
pixel[3] = alpha;
} else {
std::cerr << "Pixel coordinates out of bounds" << std::endl;
}
}
void getPixel(int x, int y, uint8_t& red, uint8_t& green, uint8_t& blue, uint8_t& alpha) const {
if (x >= 0 && x < width && y >= 0 && y < height) {
const uint8_t* pixel = buffer + (y * width + x) * 4;
red = pixel[0];
green = pixel[1];
blue = pixel[2];
alpha = pixel[3];
} else {
std::cerr << "Pixel coordinates out of bounds" << std::endl;
}
}
void fillRectangle(int x, int y, int rectWidth, int rectHeight, uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) {
// クリッピング処理
int xEnd = std::min(x + rectWidth, width);
int yEnd = std::min(y + rectHeight, height);
x = std::max(x, 0);
y = std::max(y, 0);
for (int j = y; j < yEnd; ++j) {
uint8_t* row = buffer + (j * width + x) * 4;
for (int i = x; i < xEnd; ++i) {
row[0] = red;
row[1] = green;
row[2] = blue;
row[3] = alpha;
row += 4;
}
}
}
int getWidth() const { return width; }
int getHeight() const { return height; }
private:
int width, height;
uint8_t* buffer;
};
int main() {
ImageBuffer img(5, 5);
img.fillRectangle(1, 1, 10, 10, 0, 255, 0, 255); // 矩形のサイズが大きすぎてもクラッシュしない
for (int y = 0; y < img.getHeight(); ++y) {
for (int x = 0; x < img.getWidth(); ++x) {
uint8_t r, g, b, a;
img.getPixel(x, y, r, g, b, a);
std::cout << "(" << (int)r << "," << (int)g << "," << (int)b << "," << (int)a << ") ";
}
std::cout << std::endl;
}
return 0;
}
この実装では、矩形の範囲外指定を避けるためにクリッピング処理を行っています。矩形の終点座標をバッファの範囲内に収めるため、xEnd および yEnd を計算し、開始点座標が負の場合はそれを 0 にクリッピングします。これにより、範囲外指定があってもクラッシュせずに正しく動作します。
他にも改善点や追加したい機能があれば教えてくださいね!
fillRectangle をアルファ値を渡して半透明対応してください
了解しました。アルファ値を考慮して、既存のピクセルと新しいピクセルをブレンドして半透明対応するように fillRectangle メソッドを更新します。以下にその実装を示します。
#include <iostream>
#include <vector>
class ImageBuffer {
public:
ImageBuffer(int width, int height)
: width(width), height(height), buffer(new uint8_t[width * height * 4]()) {}
~ImageBuffer() {
delete[] buffer;
}
void setPixel(int x, int y, uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) {
if (x >= 0 && x < width && y >= 0 && y < height) {
uint8_t* pixel = buffer + (y * width + x) * 4;
pixel[0] = red;
pixel[1] = green;
pixel[2] = blue;
pixel[3] = alpha;
} else {
std::cerr << "Pixel coordinates out of bounds" << std::endl;
}
}
void getPixel(int x, int y, uint8_t& red, uint8_t& green, uint8_t& blue, uint8_t& alpha) const {
if (x >= 0 && x < width && y >= 0 && y < height) {
const uint8_t* pixel = buffer + (y * width + x) * 4;
red = pixel[0];
green = pixel[1];
blue = pixel[2];
alpha = pixel[3];
} else {
std::cerr << "Pixel coordinates out of bounds" << std::endl;
}
}
void fillRectangle(int x, int y, int rectWidth, int rectHeight, uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) {
int xEnd = std::min(x + rectWidth, width);
int yEnd = std::min(y + rectHeight, height);
x = std::max(x, 0);
y = std::max(y, 0);
for (int j = y; j < yEnd; ++j) {
uint8_t* row = buffer + (j * width + x) * 4;
for (int i = x; i < xEnd; ++i) {
uint8_t* pixel = row + (i - x) * 4;
blendPixel(pixel, red, green, blue, alpha);
}
}
}
void blendPixel(uint8_t* pixel, uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) {
float alphaSrc = alpha / 255.0f;
float alphaDst = pixel[3] / 255.0f;
float outAlpha = alphaSrc + alphaDst * (1.0f - alphaSrc);
pixel[0] = static_cast((red * alphaSrc + pixel[0] * alphaDst * (1.0f - alphaSrc)) / outAlpha);
pixel[1] = static_cast((green * alphaSrc + pixel[1] * alphaDst * (1.0f - alphaSrc)) / outAlpha);
pixel[2] = static_cast((blue * alphaSrc + pixel[2] * alphaDst * (1.0f - alphaSrc)) / outAlpha);
pixel[3] = static_cast(outAlpha * 255);
}
int getWidth() const { return width; }
int getHeight() const { return height; }
private:
int width, height;
uint8_t* buffer;
};
int main() {
ImageBuffer img(5, 5);
img.fillRectangle(1, 1, 3, 3, 0, 255, 0, 128); // 半透明の緑色で矩形を塗りつぶす
for (int y = 0; y < img.getHeight(); ++y) {
for (int x = 0; x < img.getWidth(); ++x) {
uint8_t r, g, b, a;
img.getPixel(x, y, r, g, b, a);
std::cout << "(" << (int)r << "," << (int)g << "," << (int)b << "," << (int)a << ") ";
}
std::cout << std::endl;
}
return 0;
}
この実装では、blendPixel メソッドを追加し、既存のピクセルと新しいピクセルをアルファブレンドしています。これにより、半透明の矩形を塗りつぶすことが可能になります。
他に追加や改善点があれば教えてくださいね!
blendPixel が浮動小数を使っていて遅いので、最適化してください
了解しました。浮動小数点計算を避けて blendPixel 関数を最適化するために、整数演算を使用します。以下にその実装を示します。
#include <iostream>
#include <vector>
#include <algorithm>
class ImageBuffer {
public:
ImageBuffer(int width, int height)
: width(width), height(height), buffer(new uint8_t[width * height * 4]()) {}
~ImageBuffer() {
delete[] buffer;
}
void setPixel(int x, int y, uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) {
if (x >= 0 && x < width && y >= 0 && y < height) {
uint8_t* pixel = buffer + (y * width + x) * 4;
pixel[0] = red;
pixel[1] = green;
pixel[2] = blue;
pixel[3] = alpha;
} else {
std::cerr << "Pixel coordinates out of bounds" << std::endl;
}
}
void getPixel(int x, int y, uint8_t& red, uint8_t& green, uint8_t& blue, uint8_t& alpha) const {
if (x >= 0 && x < width && y >= 0 && y < height) {
const uint8_t* pixel = buffer + (y * width + x) * 4;
red = pixel[0];
green = pixel[1];
blue = pixel[2];
alpha = pixel[3];
} else {
std::cerr << "Pixel coordinates out of bounds" << std::endl;
}
}
void fillRectangle(int x, int y, int rectWidth, int rectHeight, uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) {
int xEnd = std::min(x + rectWidth, width);
int yEnd = std::min(y + rectHeight, height);
x = std::max(x, 0);
y = std::max(y, 0);
for (int j = y; j < yEnd; ++j) {
uint8_t* row = buffer + (j * width + x) * 4;
for (int i = x; i < xEnd; ++i) {
blendPixel(row, red, green, blue, alpha);
row += 4;
}
}
}
void blendPixel(uint8_t* pixel, uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) {
int invAlpha = 255 - alpha;
pixel[0] = (red * alpha + pixel[0] * invAlpha) / 255;
pixel[1] = (green * alpha + pixel[1] * invAlpha) / 255;
pixel[2] = (blue * alpha + pixel[2] * invAlpha) / 255;
pixel[3] = (alpha + pixel[3] * invAlpha) / 255;
}
int getWidth() const { return width; }
int getHeight() const { return height; }
private:
int width, height;
uint8_t* buffer;
};
int main() {
ImageBuffer img(5, 5);
img.fillRectangle(1, 1, 3, 3, 0, 255, 0, 128); // 半透明の緑色で矩形を塗りつぶす
for (int y = 0; y < img.getHeight(); ++y) {
for (int x = 0; x < img.getWidth(); ++x) {
uint8_t r, g, b, a;
img.getPixel(x, y, r, g, b, a);
std::cout << "(" << (int)r << "," << (int)g << "," << (int)b << "," << (int)a << ") ";
}
std::cout << std::endl;
}
return 0;
}
この最適化では、浮動小数点演算を避けるために整数演算を使用し、アルファブレンドを実行しています。これにより、パフォーマンスが向上します。他に追加や改善点があれば教えてくださいね!
A,R,G,B値をもつピクセル型を用意して、それを適用してください
了解しました。Pixel 型を用意し、それを使用して ImageBuffer クラスに適用します。以下にその実装を示します。
まず、Pixel 型を定義します。
struct Pixel {
uint8_t red;
uint8_t green;
uint8_t blue;
uint8_t alpha;
};
次に、この Pixel 型を ImageBuffer クラスに適用します。
#include <iostream>
#include <vector>
#include <algorithm>
struct Pixel {
uint8_t red;
uint8_t green;
uint8_t blue;
uint8_t alpha;
};
class ImageBuffer {
public:
ImageBuffer(int width, int height)
: width(width), height(height), buffer(new Pixel[width * height]()) {}
~ImageBuffer() {
delete[] buffer;
}
void setPixel(int x, int y, const Pixel& pixel) {
if (x >= 0 && x < width && y >= 0 && y < height) {
buffer[y * width + x] = pixel;
} else {
std::cerr << "Pixel coordinates out of bounds" << std::endl;
}
}
void getPixel(int x, int y, Pixel& pixel) const {
if (x >= 0 && x < width && y >= 0 && y < height) {
pixel = buffer[y * width + x];
} else {
std::cerr << "Pixel coordinates out of bounds" << std::endl;
}
}
void fillRectangle(int x, int y, int rectWidth, int rectHeight, const Pixel& pixel) {
int xEnd = std::min(x + rectWidth, width);
int yEnd = std::min(y + rectHeight, height);
x = std::max(x, 0);
y = std::max(y, 0);
for (int j = y; j < yEnd; ++j) {
Pixel* row = buffer + (j * width + x);
for (int i = x; i < xEnd; ++i) {
blendPixel(row[i], pixel);
}
}
}
void blendPixel(Pixel& dest, const Pixel& src) {
int invAlpha = 255 - src.alpha;
dest.red = (src.red * src.alpha + dest.red * invAlpha) / 255;
dest.green = (src.green * src.alpha + dest.green * invAlpha) / 255;
dest.blue = (src.blue * src.alpha + dest.blue * invAlpha) / 255;
dest.alpha = (src.alpha + dest.alpha * invAlpha) / 255;
}
int getWidth() const { return width; }
int getHeight() const { return height; }
private:
int width, height;
Pixel* buffer;
};
int main() {
ImageBuffer img(5, 5);
Pixel green = {0, 255, 0, 128};
img.fillRectangle(1, 1, 3, 3, green); // 半透明の緑色で矩形を塗りつぶす
for (int y = 0; y < img.getHeight(); ++y) {
for (int x = 0; x < img.getWidth(); ++x) {
Pixel pixel;
img.getPixel(x, y, pixel);
std::cout << "(" << (int)pixel.red << "," << (int)pixel.green << "," << (int)pixel.blue << "," << (int)pixel.alpha << ") ";
}
std::cout << std::endl;
}
return 0;
}
この実装では、Pixel 型を定義し、ImageBuffer クラスに適用しました。各ピクセル操作で Pixel 型を使用して、コードの可読性と保守性を向上させました。これで、半透明の矩形を簡単に塗りつぶすことができます。
他に追加や改善点があれば、教えてくださいね!
幅と高さを指定して、後からリサイズできるようにしてください
了解しました。それでは、ImageBuffer クラスにリサイズ機能を追加して、幅と高さを指定して画像バッファをリサイズできるようにしましょう。
以下にその実装を示します。
#include <iostream>
#include <vector>
#include <algorithm>
struct Pixel {
uint8_t red;
uint8_t green;
uint8_t blue;
uint8_t alpha;
};
class ImageBuffer {
public:
ImageBuffer(int width, int height)
: width(width), height(height), buffer(new Pixel[width * height]()) {}
~ImageBuffer() {
delete[] buffer;
}
void setPixel(int x, int y, const Pixel& pixel) {
if (x >= 0 && x < width && y >= 0 && y < height) {
buffer[y * width + x] = pixel;
} else {
std::cerr << "Pixel coordinates out of bounds" << std::endl;
}
}
void getPixel(int x, int y, Pixel& pixel) const {
if (x >= 0 && x < width && y >= 0 && y < height) {
pixel = buffer[y * width + x];
} else {
std::cerr << "Pixel coordinates out of bounds" << std::endl;
}
}
void fillRectangle(int x, int y, int rectWidth, int rectHeight, const Pixel& pixel) {
int xEnd = std::min(x + rectWidth, width);
int yEnd = std::min(y + rectHeight, height);
x = std::max(x, 0);
y = std::max(y, 0);
for (int j = y; j < yEnd; ++j) {
Pixel* row = buffer + (j * width + x);
for (int i = x; i < xEnd; ++i) {
blendPixel(row[i], pixel);
}
}
}
void resize(int newWidth, int newHeight) {
Pixel* newBuffer = new Pixel[newWidth * newHeight]();
int minWidth = std::min(width, newWidth);
int minHeight = std::min(height, newHeight);
for (int y = 0; y < minHeight; ++y) {
for (int x = 0; x < minWidth; ++x) {
newBuffer[y * newWidth + x] = buffer[y * width + x];
}
}
delete[] buffer;
buffer = newBuffer;
width = newWidth;
height = newHeight;
}
void blendPixel(Pixel& dest, const Pixel& src) {
int invAlpha = 255 - src.alpha;
dest.red = (src.red * src.alpha + dest.red * invAlpha) / 255;
dest.green = (src.green * src.alpha + dest.green * invAlpha) / 255;
dest.blue = (src.blue * src.alpha + dest.blue * invAlpha) / 255;
dest.alpha = (src.alpha + dest.alpha * invAlpha) / 255;
}
int getWidth() const { return width; }
int getHeight() const { return height; }
private:
int width, height;
Pixel* buffer;
};
int main() {
ImageBuffer img(5, 5);
Pixel green = {0, 255, 0, 128};
img.fillRectangle(1, 1, 3, 3, green); // 半透明の緑色で矩形を塗りつぶす
std::cout << "Before resizing:" << std::endl;
for (int y = 0; y < img.getHeight(); ++y) {
for (int x = 0; x < img.getWidth(); ++x) {
Pixel pixel;
img.getPixel(x, y, pixel);
std::cout << "(" << (int)pixel.red << "," << (int)pixel.green << "," << (int)pixel.blue << "," << (int)pixel.alpha << ") ";
}
std::cout << std::endl;
}
img.resize(7, 7);
std::cout << "After resizing:" << std::endl;
for (int y = 0; y < img.getHeight(); ++y) {
for (int x = 0; x < img.getWidth(); ++x) {
Pixel pixel;
img.getPixel(x, y, pixel);
std::cout << "(" << (int)pixel.red << "," << (int)pixel.green << "," << (int)pixel.blue << "," << (int)pixel.alpha << ") ";
}
std::cout << std::endl;
}
return 0;
}
この実装では、resize メソッドを追加し、幅と高さを指定して画像バッファをリサイズできるようにしました。リサイズ時に新しいバッファを確保し、既存のピクセルデータをコピーしています。
追加や改善点があれば教えてくださいね!
結論:優秀ですね!
いやぁ、優秀ですね。コンパイルしてないけど、ちゃんと動きそうです。
貼り付けが面倒臭いので、記事としてはこんなところで。どれだけ要求を聞いてくれて、どこで破綻するのか、限界まで試してみたいですね。