このページは、練習問題の解答例や解説のページです。
.bmp の読み込みと書き出しを行う関数を、bmp.cpp と bmp.h に整理してください。
関数自体は本編で取り上げたとおりです。bmp.cpp に定義を、bmp.h に宣言を配置します。Color構造体も必要になりますが、これは .bmp に付属するものというより、ペイントスクリプト全体で使いどころがあるものなので、color.h を作って独立させておきます。
//bmp.cpp
#include "bmp.h"
#include <cassert>
#include <fstream>
#include "color.h"
bool save_bmp(const std::string& path, unsigned int width, unsigned int height, const std::vector<std::vector<Color>>& pixels)
{
std::ofstream ofs(path, std::ios_base::out | std::ios_base::binary);
if (!ofs) {
return false;
}
// ----- ファイルヘッダ部 -----
// Windows API の BITMAPFILEHEADER構造体にあたる。
// https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapfileheader
// ファイルタイプ
// 必ず 0x4d42 ("BM" のこと)
constexpr std::uint16_t file_type {0x4d42};
.write(reinterpret_cast<const char*>(&file_type), sizeof(file_type));
ofs
// ファイルサイズ
// 縦横のピクセル数 * 1ピクセル当たりのバイト数(PaintScript では 4バイト固定) + ヘッダ部のバイト数。
// ヘッダ部の大きさは、sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) より。
const std::uint32_t file_size {width * height * 4 + 54};
.write(reinterpret_cast<const char*>(&file_size), sizeof(file_size));
ofs
// 予約領域
const std::uint32_t reserved {0};
.write(reinterpret_cast<const char*>(&reserved), sizeof(reserved));
ofs
// ファイル先頭から、ピクセル情報までの距離
const std::uint32_t offset_to_pixels {54};
.write(reinterpret_cast<const char*>(&offset_to_pixels), sizeof(offset_to_pixels));
ofs
// ----- ビットマップ情報ヘッダ部 -----
// Windows API の _BITMAPINFOHEADER構造体にあたる。
// https://learn.microsoft.com/ja-jp/windows/win32/wmdm/-bitmapinfoheader
// ビットマップ情報ヘッダ部のサイズ
const std::uint32_t bitmap_info_header_size {40};
.write(reinterpret_cast<const char*>(&bitmap_info_header_size), sizeof(bitmap_info_header_size));
ofs
// 横方向のピクセル数
const std::int32_t w {static_cast<std::int32_t>(width)};
.write(reinterpret_cast<const char*>(&w), sizeof(w));
ofs
// 縦方向のピクセル数
const std::int32_t h {static_cast<std::int32_t>(height)};
.write(reinterpret_cast<const char*>(&h), sizeof(h));
ofs
// プレーン数。必ず 1
const std::uint16_t planes {1};
.write(reinterpret_cast<const char*>(&planes), sizeof(planes));
ofs
// 1ピクセル当たりのビット数。PaintScript では 24 に固定
const std::uint16_t bit_count {24};
.write(reinterpret_cast<const char*>(&bit_count), sizeof(bit_count));
ofs
// 圧縮形式。無圧縮は 0
const std::uint32_t compression {0};
.write(reinterpret_cast<const char*>(&compression), sizeof(compression));
ofs
// 画像サイズ。無圧縮であれば 0 で構わない
const std::uint32_t image_size {0};
.write(reinterpret_cast<const char*>(&image_size), sizeof(image_size));
ofs
// メートル当たりの横方向のピクセル数の指示。不要なら 0 にできる
const std::int32_t x_pixels_per_meter {0};
.write(reinterpret_cast<const char*>(&x_pixels_per_meter), sizeof(x_pixels_per_meter));
ofs
// メートル当たりの縦方向のピクセル数の指示。不要なら 0 にできる
const std::int32_t y_pixels_per_meter {0};
.write(reinterpret_cast<const char*>(&y_pixels_per_meter), sizeof(y_pixels_per_meter));
ofs
// カラーテーブル内の色のうち、実際に使用している個数。パレット形式でなければ無関係
const std::uint32_t clr_used {0};
.write(reinterpret_cast<const char*>(&clr_used), sizeof(clr_used));
ofs
// カラーテーブル内の色のうち、重要色である色の個数。パレット形式でなければ無関係
const std::uint32_t clr_important {0};
.write(reinterpret_cast<const char*>(&clr_important), sizeof(clr_important));
ofs
// ----- ピクセル情報 -----
// 各ピクセルは RGB の各8bit である想定。
for (std::int32_t y {h - 1}; y >= 0; --y) {
for (std::int32_t x {0}; x < w; ++x) {
const Color pixel {pixels.at(y).at(x)};
.write(reinterpret_cast<const char*>(&pixel.blue), sizeof(pixel.blue));
ofs.write(reinterpret_cast<const char*>(&pixel.green), sizeof(pixel.green));
ofs.write(reinterpret_cast<const char*>(&pixel.red), sizeof(pixel.red));
ofs}
}
return true;
}
bool load_bmp(const std::string& path, unsigned int* width, unsigned int* height, std::vector<std::vector<Color>>* pixels)
{
assert(width);
assert(height);
assert(pixels);
std::ifstream ifs(path, std::ios_base::in | std::ios_base::binary);
if (!ifs) {
return false;
}
// 不要なところを読み飛ばす
.seekg(18, std::ios_base::beg);
ifs
// 横方向のピクセル数
std::int32_t w {};
.read(reinterpret_cast<char*>(&w), sizeof(w));
ifsif (w < 1) {
return false;
}
*width = static_cast<unsigned int>(w);
// 縦方向のピクセル数
std::int32_t h {};
.read(reinterpret_cast<char*>(&h), sizeof(h));
ifsif (h < 1) {
return false;
}
*height = static_cast<unsigned int>(h);
// 不要なところを読み飛ばす
.seekg(28, std::ios_base::cur);
ifs
// ピクセル情報
// vector は、ビットマップの大きさに合わせて resize する。
->resize(h);
pixelsfor (auto& row : *pixels) {
.resize(w);
row}
for (std::int32_t y {h - 1}; y >= 0; --y) {
for (std::int32_t x {0}; x < w; ++x) {
std::uint8_t b {};
.read(reinterpret_cast<char*>(&b), sizeof(b));
ifsstd::uint8_t g {};
.read(reinterpret_cast<char*>(&g), sizeof(g));
ifsstd::uint8_t r {};
.read(reinterpret_cast<char*>(&r), sizeof(r));
ifs
->at(y).at(x) = Color{r, g, b};
pixels}
}
return true;
}
//bmp.h
#ifndef BMP_H_INCLUDED
#define BMP_H_INCLUDED
#include <string>
#include <vector>
struct Color;
// .bmpファイルに書き出す
//
// path: ファイルパス
// width: 横方向のピクセル数
// height: 縦方向のピクセル数
// pixels: ピクセル情報
// 戻り値: 成否
bool save_bmp(const std::string& path, unsigned int width, unsigned int height, const std::vector<std::vector<Color>>& pixels);
// .bmpファイルを読み込む
//
// path: ファイルパス
// width: 横方向のピクセル数を受け取るポインタ。ヌルポインタ不可
// height: 縦方向のピクセル数を受け取るポインタ。ヌルポインタ不可
// pixels: ピクセル情報を受け取るポインタ。ヌルポインタ不可
// 戻り値: 成否
bool load_bmp(const std::string& path, unsigned int* width, unsigned int* height, std::vector<std::vector<Color>>* pixels);
#endif
// color.h
#ifndef COLOR_H_INCLUDED
#define COLOR_H_INCLUDED
// 色
struct Color {
unsigned char red; // 赤成分
unsigned char green; // 緑成分
unsigned char blue; // 青成分
};
#endif
最低限の動作確認をしておきましょう。test.bmp を読み込んで、そのまま result.bmp として書き出してみます。
//main.cpp
#include <cstdlib>
#include <iostream>
#include "bmp.h"
#include "color.h"
int main()
{
unsigned int width {};
unsigned int height {};
std::vector<std::vector<Color>> pixels {};
if (!load_bmp("test.bmp", &width, &height, &pixels)) {
std::cerr << "load error.\n";
std::quick_exit(EXIT_FAILURE);
}
if (!save_bmp("result.bmp", width, height, pixels)) {
std::cerr << "save error.\n";
std::quick_exit(EXIT_FAILURE);
}
}
.bmpファイルを読み込み、ネガポジ変換を行った結果を、新たな .bmpファイルとして書き出すプログラムを作成してください。
ネガポジ変換とは、RGB のそれぞれの強弱を逆転させる色変換です(ネガポジは、ネガティブとポジティブのことです)。たとえば、R(赤) が 255、G(緑) が 200、B(青) が 50 のピクセルが、R = 0、G = 55、B = 205 となるように変換します。
ネガポジ変換は、RGB それぞれの強弱を逆転させれば実現できるので、最大値のときに最小値に、最小値のときに最大値になるように変換すればいいということです。強さの範囲を 0~255 としているので、0 は 255 に、1 は 254 に、2 は 253 に・・・と続き、255 は 0 になります。つまり「255 - 元の強さ」という計算で実現できます。つまり次のようになります。
// 色を反転させる
(Color color)
Color invert_color{
return Color {
static_cast<unsigned char>(255 - color.red),
static_cast<unsigned char>(255 - color.green),
static_cast<unsigned char>(255 - color.blue)
};
}
あるいはビット単位NOT で全ビットを反転させても同じ結果になります(「ビット単位の処理」のページを参照)。
// 色を反転させる
(Color color)
Color invert_color{
return Color {
static_cast<unsigned char>(~color.red),
static_cast<unsigned char>(~color.green),
static_cast<unsigned char>(~color.blue)
};
}
このような操作をビットマップ画像内のすべてのピクセルに対して行えばいいということです。問題1で作っておいた bmp.cpp、bmp.h、color.h を使って、次のように実装できます。
//main.cpp
#include <cstdlib>
#include <iostream>
#include "bmp.h"
#include "color.h"
// 色を反転させる
(Color color)
Color invert_color{
return Color {
static_cast<unsigned char>(255 - color.red),
static_cast<unsigned char>(255 - color.green),
static_cast<unsigned char>(255 - color.blue)
};
}
int main()
{
unsigned int width {};
unsigned int height {};
std::vector<std::vector<Color>> pixels {};
if (!load_bmp("test.bmp", &width, &height, &pixels)) {
std::cerr << "load error.\n";
std::quick_exit(EXIT_FAILURE);
}
// ネガポジ変換
for (unsigned int y{0}; y < height; ++y) {
for (unsigned int x{0}; x < width; ++x) {
.at(y).at(x) = invert_color(pixels.at(y).at(x));
pixels}
}
if (!save_bmp("result.bmp", width, height, pixels)) {
std::cerr << "save error.\n";
std::quick_exit(EXIT_FAILURE);
}
}
以下の test.bmp に対して変換すると、
次のような画像に変換されます。
はてなブックマーク に保存 | Pocket に保存 | Facebook でシェア |
X で ポスト/フォロー | LINE で送る | noteで書く |
RSS | 管理者情報 | プライバシーポリシー |