/* * Copyright (C) 2016 Square, Inc. * * 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 * * http://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. */ package com.publicobject.bytes; import java.io.File; import java.io.IOException; import okio.BufferedSink; import okio.Okio; public final class Bitmap { private final int[][] pixels; public Bitmap(int[][] pixels) { this.pixels = pixels; } /** https://en.wikipedia.org/wiki/BMP_file_format */ public void encode(BufferedSink sink) throws IOException { int height = pixels.length; int width = pixels[0].length; int bytesPerPixel = 3; int rowByteCountWithoutPadding = (bytesPerPixel * width); int rowByteCount = ((rowByteCountWithoutPadding + 3) / 4) * 4; int pixelDataSize = rowByteCount * height; int bmpHeaderSize = 14; int dibHeaderSize = 40; // BMP Header sink.writeUtf8("BM"); // ID. sink.writeIntLe(bmpHeaderSize + dibHeaderSize + pixelDataSize); // File size. sink.writeShortLe(0); // Unused. sink.writeShortLe(0); // Unused. sink.writeIntLe(bmpHeaderSize + dibHeaderSize); // Offset of pixel data. // DIB Header sink.writeIntLe(dibHeaderSize); sink.writeIntLe(width); sink.writeIntLe(height); sink.writeShortLe(1); // Color plane count. sink.writeShortLe(bytesPerPixel * Byte.SIZE); sink.writeIntLe(0); // No compression. sink.writeIntLe(16); // Size of bitmap data including padding. sink.writeIntLe(2835); // Horizontal print resolution in pixels/meter. (72 dpi). sink.writeIntLe(2835); // Vertical print resolution in pixels/meter. (72 dpi). sink.writeIntLe(0); // Palette color count. sink.writeIntLe(0); // 0 important colors. // Pixel data. for (int y = height - 1; y >= 0; y--) { int[] row = pixels[y]; for (int x = 0; x < width; x++) { int pixel = row[x]; sink.writeByte((pixel & 0x0000ff)); // b sink.writeByte((pixel & 0x00ff00) >> 8); // g sink.writeByte((pixel & 0xff0000) >> 16); // r } // Padding for 4-byte alignment. for (int p = rowByteCountWithoutPadding; p < rowByteCount; p++) { sink.writeByte(0); } } } private void encodeToFile(File file) throws IOException { try (BufferedSink sink = Okio.buffer(Okio.sink(file))) { encode(sink); } } /** Generates bitmaps with gradients from each of the four corners. */ public static final class GradientBitmapFactory { final int width; final int height; final int size; final int nw; final int ne; final int se; final int sw; public GradientBitmapFactory(int width, int height, int nw, int ne, int se, int sw) { this.width = width; this.height = height; this.size = width * height; this.nw = nw; this.ne = ne; this.se = se; this.sw = sw; } public Bitmap create() { int[][] pixels = new int[height][]; for (int y = 0; y < height; y++) { pixels[y] = new int[width]; for (int x = 0; x < width; x++) { pixels[y][x] = color(x, y); } } return new Bitmap(pixels); } /** Returns the color of the pixel at {@code x, y}. */ private int color(int x, int y) { return subpixelColor(x, y, 0xff0000, 16) | subpixelColor(x, y, 0x00ff00, 8) | subpixelColor(x, y, 0x0000ff, 0); } /** * Use {@code mask} and {@code shift} to extract a single component (red, green, or blue) from * each of the corners and compute its color at {@code x, y}. */ private int subpixelColor(int x, int y, int mask, int shift) { int nx = width - x; int ny = height - y; int a = ((nw & mask) >> shift) * nx * ny / size; int b = ((ne & mask) >> shift) * x * ny / size; int c = ((se & mask) >> shift) * x * y / size; int d = ((sw & mask) >> shift) * nx * y / size; return (a + b + c + d) << shift; } } public static void main(String[] args) throws IOException { Bitmap sample = new Bitmap(new int[][] { new int[] {0x0000ff, /* Blue. */ 0x00ff00 /* Green. */}, new int[] {0xff0000, /* Red. */ 0xffffff /* White. */} }); sample.encodeToFile(new File("sample.bmp")); Bitmap gradient = new GradientBitmapFactory(300, 150, 0xffff00, /* Yellow. */ 0x000000 /* Black. */, 0x00ffff, /* Cyan. */ 0xffffff /* White. */).create(); gradient.encodeToFile(new File("gradient.bmp")); } }