blob: d4e76ea6573978e5f08f8950d33d1e40d30d0b6e [file] [log] [blame]
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// -----------------------------------------------------------------------------
//
// BMP Image writer
#include <cstdint>
#include <cstdio>
#include "imageio/image_enc.h"
#include "src/utils/utils.h"
#include "src/wp2/base.h"
namespace WP2 {
namespace {
void PutLE16(uint8_t* const dst, uint32_t value) {
dst[0] = (value >> 0) & 0xff;
dst[1] = (value >> 8) & 0xff;
}
void PutLE32(uint8_t* const dst, uint32_t value) {
PutLE16(dst + 0, (value >> 0) & 0xffff);
PutLE16(dst + 2, (value >> 16) & 0xffff);
}
} // namespace
static constexpr uint32_t kBMPHeaderSize = 54u;
static constexpr uint32_t kBMPAlphaHeaderSize = 70u; // + 16 bytes of masks
WP2Status WriteBMP(const ArgbBuffer& buffer, FILE* fout) {
WP2_CHECK_OK(fout != nullptr, WP2_STATUS_NULL_PARAMETER);
WP2_CHECK_OK(!buffer.IsEmpty(), WP2_STATUS_NULL_PARAMETER);
WP2_CHECK_OK(WP2Formatbpc(buffer.format()) == 8,
WP2_STATUS_INVALID_PARAMETER);
const bool has_alpha = buffer.HasTransparency();
ArgbBuffer converted_row(has_alpha ? WP2_BGRA_32 : WP2_BGR_24);
const uint8_t bytes_per_px = WP2FormatBpp(converted_row.format());
const uint32_t width = buffer.width();
const uint32_t height = buffer.height();
const uint32_t line_size = bytes_per_px * width;
const uint32_t bmp_stride = (line_size + 3) & ~3; // pad to 4
const uint32_t header_size = has_alpha ? kBMPAlphaHeaderSize : kBMPHeaderSize;
const uint32_t total_size = bmp_stride * height + header_size;
const uint32_t format = has_alpha ? 3 : 0; // BI_BITFIELDS or BI_RGB
// bitmap file header
uint8_t bmp_header[kBMPAlphaHeaderSize] = {0};
PutLE16(bmp_header + 0, 0x4d42); // signature 'BM'
PutLE32(bmp_header + 2, total_size); // size including header
PutLE32(bmp_header + 6, 0); // reserved
PutLE32(bmp_header + 10, header_size); // offset to pixel array
// bitmap info header
PutLE32(bmp_header + 14, header_size - 14); // DIB header size
PutLE32(bmp_header + 18, width); // dimensions
PutLE32(bmp_header + 22, height); // no vertical flip
PutLE16(bmp_header + 26, 1); // number of planes
PutLE16(bmp_header + 28, bytes_per_px * 8); // bits per pixel
PutLE32(bmp_header + 30, format); // compression format
PutLE32(bmp_header + 34, 0); // image size (dummy)
PutLE32(bmp_header + 38, 2400); // x pixels/meter
PutLE32(bmp_header + 42, 2400); // y pixels/meter
PutLE32(bmp_header + 46, 0); // number of palette colors
PutLE32(bmp_header + 50, 0); // important color count
if (format == 3) { // BITMAPV3INFOHEADER complement
PutLE32(bmp_header + 54, 0x00ff0000); // red mask
PutLE32(bmp_header + 58, 0x0000ff00); // green mask
PutLE32(bmp_header + 62, 0x000000ff); // blue mask
PutLE32(bmp_header + 66, 0xff000000); // alpha mask
}
// TODO(skal): color profile
// write header
WP2_CHECK_OK(fwrite(bmp_header, header_size, 1, fout) == 1,
WP2_STATUS_BAD_WRITE);
// write pixel array
for (uint32_t y = 0; y < height; ++y) {
ArgbBuffer buffer_row(buffer.format());
WP2_CHECK_STATUS(buffer_row.SetView(buffer, {0, height - y - 1, width, 1}));
if (converted_row.format() == buffer_row.format()) {
WP2_CHECK_STATUS(converted_row.SetView(buffer_row));
} else {
WP2_CHECK_STATUS(converted_row.ConvertFrom(buffer_row));
}
WP2_CHECK_OK(fwrite(converted_row.GetRow8(0), bytes_per_px,
converted_row.width(), fout) == converted_row.width(),
WP2_STATUS_BAD_WRITE);
// Pad till the line length is a multiple of 4 bytes.
if (bmp_stride > line_size) {
const uint8_t padding[3] = {0, 0, 0};
WP2_CHECK_OK(fwrite(padding, bmp_stride - line_size, 1, fout) == 1,
WP2_STATUS_BAD_WRITE);
}
}
return WP2_STATUS_OK;
}
} // namespace WP2