blob: 2cc4df018c81abebf3b85987a3a528e73d27d7ff [file] [log] [blame]
import 'dart:typed_data';
import '../image.dart';
import 'hdr_slice.dart';
/// A high dynamic range RGBA image stored in 16-bit or 32-bit floating-point
/// channels.
class HdrImage {
static const int HALF = 1;
static const int FLOAT = 2;
static const int UINT = 0;
/// Red value of a sample
static const String R = 'R';
/// Green value of a sample
static const String G = 'G';
/// Blue value of a sample
static const String B = 'B';
/// Alpha/opacity
static const String A = 'A';
/// Distance of the front of a sample from the viewer
static const String Z = 'Z';
/// A numerical identifier for the object represented by a sample.
static const String ID = 'id';
final Map<String, HdrSlice> slices = {};
HdrSlice red;
HdrSlice green;
HdrSlice blue;
HdrSlice alpha;
HdrSlice depth;
HdrImage();
/// Create an RGB[A] image.
HdrImage.create(int width, int height, int channels, int format) {
if (channels < 0 || channels > 4) {
return;
}
if (format != HALF && format != FLOAT && format != UINT) {
return;
}
const List<String> channelList = const [R, G, B, A];
for (int i = 0; i < channels; ++i) {
addSlice(new HdrSlice(channelList[i], width, height, format));
}
}
/// Create a copy of the [other] HdrImage.
HdrImage.from(HdrImage other) {
for (String ch in other.slices.keys) {
HdrSlice slice = other.slices[ch];
addSlice(new HdrSlice.from(slice));
}
}
/// Create an HDR image from a LDR [Image] by transforming the channel values
/// to the range [0, 1].
HdrImage.fromImage(Image other) {
addSlice(new HdrSlice(R, other.width, other.height, HALF));
addSlice(new HdrSlice(G, other.width, other.height, HALF));
addSlice(new HdrSlice(B, other.width, other.height, HALF));
if (other.format == Image.RGBA) {
addSlice(new HdrSlice(A, other.width, other.height, HALF));
}
Uint8List rgb = other.getBytes();
for (int y = 0, si = 0; y < other.height; ++y) {
for (int x = 0; x < other.width; ++x) {
red.setFloat(x, y, rgb[si++] / 255.0);
green.setFloat(x, y, rgb[si++] / 255.0);
blue.setFloat(x, y, rgb[si++] / 255.0);
if (alpha != null) {
alpha.setFloat(x, y, rgb[si++] / 255.0);
}
}
}
}
/// Does the image have any color channels?
bool get hasColor => red != null || green != null || blue != null;
/// Does the image have an alpha channel?
bool get hasAlpha => alpha != null;
/// Does the image have a depth channel?
bool get hasDepth => depth != null;
/// The width of the framebuffer.
int get width => slices.isEmpty ? 0 : slices.values.first.width;
/// The height of the framebuffer.
int get height => slices.isEmpty ? 0 : slices.values.first.height;
/// Get the value of the red channel at the given pixel coordinates [x], [y].
double getRed(int x, int y) {
return red != null ? red.getFloat(x, y) : 0.0;
}
/// Set the value of the red channel at the given pixel coordinates [x], [y].
void setRed(int x, int y, num c) {
if (red != null) {
red.setFloat(x, y, c);
}
}
void setRedInt(int x, int y, int c) {
if (red != null) {
red.setInt(x, y, c);
}
}
/// Get the value of the green channel at the given pixel coordinates [x], [y].
double getGreen(int x, int y) {
return green != null ? green.getFloat(x, y) : 0.0;
}
/// Set the value of the green channel at the given pixel coordinates [x], [y].
void setGreen(int x, int y, num c) {
if (green != null) {
green.setFloat(x, y, c);
}
}
void setGreenInt(int x, int y, int c) {
if (green != null) {
green.setInt(x, y, c);
}
}
/// Get the value of the blue channel at the given pixel coordinates [x], [y].
double getBlue(int x, int y) {
return blue != null ? blue.getFloat(x, y) : 0.0;
}
/// Set the value of the blue channel at the given pixel coordinates [x], [y].
void setBlue(int x, int y, num c) {
if (blue != null) {
blue.setFloat(x, y, c);
}
}
void setBlueInt(int x, int y, int c) {
if (blue != null) {
blue.setInt(x, y, c);
}
}
/// Get the value of the alpha channel at the given pixel coordinates [x], [y].
double getAlpha(int x, int y) {
return alpha != null ? alpha.getFloat(x, y) : 0.0;
}
/// Set the value of the alpha channel at the given pixel coordinates [x], [y].
void setAlpha(int x, int y, double c) {
if (alpha != null) {
alpha.setFloat(x, y, c);
}
}
void setAlphaInt(int x, int y, int c) {
if (alpha != null) {
alpha.setInt(x, y, c);
}
}
/// Get the value of the depth channel at the given pixel coordinates [x], [y].
double getDepth(int x, int y) {
return depth != null ? depth.getFloat(x, y) : 0.0;
}
/// Set the value of the depth channel at the given pixel coordinates [x], [y].
void setDepth(int x, int y, double c) {
if (depth != null) {
depth.setFloat(x, y, c);
}
}
/// Does this image contain the given channel?
bool hasChannel(String ch) => slices.containsKey(ch);
/// Access a framebuffer slice by name.
HdrSlice operator [](String ch) => slices[ch];
/// Add a channel [slice] to the
void addSlice(HdrSlice slice) {
String ch = slice.name;
slices[ch] = slice;
switch (ch) {
case R:
red = slice;
break;
case G:
green = slice;
break;
case B:
blue = slice;
break;
case A:
alpha = slice;
break;
case Z:
depth = slice;
break;
}
}
/// Convert the framebuffer to an floating-point image, as a sequence of
/// floats in RGBA order.
Float32List toFloatRgba() {
Float32List rgba = Float32List(width * height * 4);
int w = width;
int h = height;
for (int y = 0, di = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
rgba[di++] = red == null ? 0.0 : red.getFloat(x, y);
rgba[di++] = green == null ? 0.0 : green.getFloat(x, y);
rgba[di++] = blue == null ? 0.0 : blue.getFloat(x, y);
rgba[di++] = alpha == null ? 1.0 : alpha.getFloat(x, y);
}
}
return rgba;
}
}