Skip to content

Instantly share code, notes, and snippets.

@swankjesse
Created September 25, 2016 03:12
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save swankjesse/0542f7f11f3ec907b5e3f2fa0607b055 to your computer and use it in GitHub Desktop.
Save swankjesse/0542f7f11f3ec907b5e3f2fa0607b055 to your computer and use it in GitHub Desktop.
/*
* 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"));
}
}
@swankjesse
Copy link
Author

swankjesse commented Sep 25, 2016

sample (2x2):
sample bmp

gradient (300 x 150):
gradient bmp

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment