Compare commits
15 commits
canvas-onl
...
master
Author | SHA1 | Date | |
---|---|---|---|
092368c694 | |||
d2fad04bfd | |||
dad9d12e2e | |||
494e335890 | |||
763512a3b1 | |||
c5a68ea94b | |||
0cc15e9b4f | |||
56994771f0 | |||
|
eb12e43d71 | ||
4a341451ba | |||
2f0de10848 | |||
33e07962bd | |||
881da03825 | |||
fb7776feac | |||
d6503bb613 |
6 changed files with 249 additions and 18 deletions
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018 Marty Oehme
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -1,2 +0,0 @@
|
||||||
|
|
||||||
Copyright (C) 2018
|
|
225
lib/Dither.dart
225
lib/Dither.dart
|
@ -1,18 +1,223 @@
|
||||||
import 'dart:html';
|
import 'dart:html';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
class Dither {
|
class Pixed {
|
||||||
static ImageData editImage(ImageData image) {
|
static PixelArray greyscale(PixelArray image) {
|
||||||
|
PixelArray pxArr = new PixelArray.fromPixelArray(image);
|
||||||
|
|
||||||
|
for (var y = 0; y < pxArr.getHeight(); y++) {
|
||||||
// image = contrast( image, 200);
|
for (var x = 0; x < pxArr.getWidth(); x++) {
|
||||||
|
var px = pxArr.getPixel(x, y);
|
||||||
for (var y = 0; y < image.height; y++) {
|
int average = ((px.r+px.g+px.b) / 3).round();
|
||||||
for (var x = 0; x < image.width; x++) {
|
px.r = average;
|
||||||
// print(image.getPixel(x, y));
|
px.g = average;
|
||||||
|
px.b = average;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return pxArr;
|
||||||
return image;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PixelArray quantize(PixelArray image, [int steps=1]) {
|
||||||
|
PixelArray pxArr = new PixelArray.fromPixelArray(image);
|
||||||
|
for (var y = 0; y < pxArr.getHeight(); y++) {
|
||||||
|
for (var x = 0; x < pxArr.getWidth(); x++) {
|
||||||
|
pxArr.setPixel( _quantizePixel(pxArr.getPixel(x, y)) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pxArr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Pixel _quantizePixel(Pixel pixel, [int steps=1]) {
|
||||||
|
return new Pixel(
|
||||||
|
pixel.x,
|
||||||
|
pixel.y,
|
||||||
|
(steps * pixel.r / 255).round() * (255 ~/ steps),
|
||||||
|
(steps * pixel.g / 255).round() * (255 ~/ steps),
|
||||||
|
(steps * pixel.b / 255).round() * (255 ~/ steps),
|
||||||
|
pixel.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PixelArray quantizeError(PixelArray original, PixelArray quantized) {
|
||||||
|
PixelArray pxArr = new PixelArray.fromPixelArray(original);
|
||||||
|
for (var y = 0; y < pxArr.getHeight(); y++) {
|
||||||
|
for (var x = 0; x < pxArr.getWidth(); x++) {
|
||||||
|
Pixel px = pxArr.getPixel(x, y);
|
||||||
|
Pixel quant = quantized.getPixel(x, y);
|
||||||
|
px.r = px.r - quant.r;
|
||||||
|
px.g = px.g - quant.g;
|
||||||
|
px.b = px.b - quant.b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pxArr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Pixel _quantizeErrorPixel(Pixel original, Pixel quantized) {
|
||||||
|
return new Pixel(original.x, original.y,
|
||||||
|
original.r - quantized.r,
|
||||||
|
original.g - quantized.g,
|
||||||
|
original.b - quantized.b,
|
||||||
|
original.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PixelArray dither(PixelArray image) {
|
||||||
|
PixelArray pxArr = new PixelArray.fromPixelArray(image);
|
||||||
|
for (var y = 0; y < pxArr.getHeight()-1; y++) {
|
||||||
|
for (var x = 1; x < pxArr.getWidth()-1; x++) {
|
||||||
|
Pixel origPx = pxArr.getPixel(x, y);
|
||||||
|
Pixel quantPx = _quantizePixel(origPx);
|
||||||
|
Pixel errPx = _quantizeErrorPixel(origPx, quantPx);
|
||||||
|
pxArr.setPixel(quantPx);
|
||||||
|
|
||||||
|
Pixel px = pxArr.getPixel(x+1, y);
|
||||||
|
px.r = (px.r + errPx.r * (7/16)).toInt();
|
||||||
|
px.g = (px.g + errPx.g * (7/16)).toInt();
|
||||||
|
px.b = (px.b + errPx.b * (7/16)).toInt();
|
||||||
|
|
||||||
|
px = pxArr.getPixel(x-1, y+1);
|
||||||
|
px.r = (px.r + errPx.r * (3/16)).toInt();
|
||||||
|
px.g = (px.g + errPx.g * (3/16)).toInt();
|
||||||
|
px.b = (px.b + errPx.b * (3/16)).toInt();
|
||||||
|
|
||||||
|
px = pxArr.getPixel(x, y+1);
|
||||||
|
px.r = (px.r + errPx.r * (5/16)).toInt();
|
||||||
|
px.g = (px.g + errPx.g * (5/16)).toInt();
|
||||||
|
px.b = (px.b + errPx.b * (5/16)).toInt();
|
||||||
|
|
||||||
|
px = pxArr.getPixel(x+1, y+1);
|
||||||
|
px.r = (px.r + errPx.r * (1/16)).toInt();
|
||||||
|
px.g = (px.g + errPx.g * (1/16)).toInt();
|
||||||
|
px.b = (px.b + errPx.b * (1/16)).toInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pxArr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Pixel {
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
int r;
|
||||||
|
int g;
|
||||||
|
int b;
|
||||||
|
int a;
|
||||||
|
|
||||||
|
Pixel(this.x, this.y, this.r, this.g, this.b, this.a);
|
||||||
|
|
||||||
|
int getBytePos(int imageWidth) {
|
||||||
|
return (x+y*imageWidth)*4;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setRGB(int r, int g, int b) {
|
||||||
|
this.r = r;
|
||||||
|
this.g = g;
|
||||||
|
this.b = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'Pixel at $x, $y: {r: $r, g: $g, b: $b, a: $a}';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class PixelArray {
|
||||||
|
//TODO Implement iterable
|
||||||
|
List<List<Pixel>> pixels;
|
||||||
|
|
||||||
|
PixelArray(int width, int height) {
|
||||||
|
pixels = new List(height);
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
pixels[y] = new List(width);
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
pixels[y][x] = new Pixel(x, y, 0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PixelArray.fromByteArray(Uint8ClampedList array, int imageWidth) {
|
||||||
|
pixels = new List(array.length~/imageWidth~/4);
|
||||||
|
int y = 0;
|
||||||
|
int x = 0;
|
||||||
|
pixels[y] = new List(imageWidth);
|
||||||
|
for (var pos = 0; pos < array.length; pos = pos + 4) {
|
||||||
|
if (x >= imageWidth) {
|
||||||
|
x = 0;
|
||||||
|
y++;
|
||||||
|
pixels[y] = new List(imageWidth);
|
||||||
|
}
|
||||||
|
pixels[y][x] = new Pixel(x, y, array[pos], array[pos+1], array[pos+2], array[pos+3]);
|
||||||
|
x++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PixelArray.fromImageData(ImageData imagedata, int imageWidth): this.fromByteArray(imagedata.data, imageWidth);
|
||||||
|
|
||||||
|
PixelArray.fromPixelArray(PixelArray pixelArray) {
|
||||||
|
pixels = pixelArray.clone().pixels;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint8ClampedList toByteArray() {
|
||||||
|
//TODO: look for longest array, or each line separately. Only gets width from line 1 currently.
|
||||||
|
Uint8ClampedList result = new Uint8ClampedList(getHeight()*getWidth()*4);
|
||||||
|
int x = 0;
|
||||||
|
int y = 0;
|
||||||
|
for (var i = 0; i < result.length; i = i + 4) {
|
||||||
|
if (x >= getWidth()) {
|
||||||
|
y++;
|
||||||
|
x = 0;
|
||||||
|
}
|
||||||
|
result[i] = pixels[y][x].r;
|
||||||
|
result[i+1] = pixels[y][x].g;
|
||||||
|
result[i+2] = pixels[y][x].b;
|
||||||
|
result[i+3] = pixels[y][x].a;
|
||||||
|
|
||||||
|
x++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageData toImageData() {
|
||||||
|
return new ImageData(this.toByteArray(), this.getWidth(), this.getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
Pixel getPixel(int x, int y) {
|
||||||
|
return pixels[y][x];
|
||||||
|
}
|
||||||
|
|
||||||
|
void setPixel(Pixel pixel, [int x, int y]) {
|
||||||
|
if (x != null && y != null && getHeight() < y && getWidth() < x) {
|
||||||
|
pixel.x = x;
|
||||||
|
pixel.y = y;
|
||||||
|
pixels[y][x] = pixel;
|
||||||
|
return;
|
||||||
|
} else if (getHeight() <= pixel.y || getWidth() <= pixel.x) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
pixels[pixel.y][pixel.x] = pixel;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int getWidth() {
|
||||||
|
//TODO find longest/shortest width not at 0 (support for non square img)
|
||||||
|
return pixels[0].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getHeight() {
|
||||||
|
return pixels.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
PixelArray clone() {
|
||||||
|
PixelArray pxArr = new PixelArray(getWidth(), getHeight());
|
||||||
|
for (var y = 0; y < getHeight(); y++) {
|
||||||
|
for (var x = 0; x < getWidth(); x++) {
|
||||||
|
Pixel px = getPixel(x, y);
|
||||||
|
Pixel newPix = new Pixel(x, y, px.r, px.g, px.b, px.a);
|
||||||
|
pxArr.setPixel( newPix, x, y );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pxArr;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -14,8 +14,8 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<canvas id="input" width="712" height="470"></canvas>
|
<canvas id="input"></canvas>
|
||||||
|
|
||||||
<canvas id="output" width="712" height="470"></canvas>
|
<canvas id="output"></canvas>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -8,11 +8,15 @@ ImageElement inputImg;
|
||||||
ImageElement outputImg;
|
ImageElement outputImg;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
inputImg = new ImageElement(src: 'kitten.jpg', width: 712, height: 470);
|
inputImg = new ImageElement(src: 'kitten.jpg');
|
||||||
|
|
||||||
input = document.querySelector('#input');
|
input = document.querySelector('#input');
|
||||||
output = document.querySelector('#output');
|
output = document.querySelector('#output');
|
||||||
|
|
||||||
|
input.width = inputImg.width;
|
||||||
|
input.height = inputImg.height;
|
||||||
|
output.width = inputImg.width;
|
||||||
|
output.height = inputImg.height;
|
||||||
inputImg.onLoad.listen(imgLoaded);
|
inputImg.onLoad.listen(imgLoaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +24,10 @@ void imgLoaded(Event e) {
|
||||||
print("image loaded");
|
print("image loaded");
|
||||||
input.context2D.drawImage(inputImg, 0, 0);
|
input.context2D.drawImage(inputImg, 0, 0);
|
||||||
|
|
||||||
output.context2D.putImageData( Dither.editImage( getImageData(input)), 0, 0);
|
PixelArray px = new PixelArray.fromImageData(getImageData(input), input.width);
|
||||||
|
px = Pixed.greyscale(px);
|
||||||
|
px = Pixed.dither(px);
|
||||||
|
output.context2D.putImageData(px.toImageData(), 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageData getImageData(CanvasElement canvas) {
|
ImageData getImageData(CanvasElement canvas) {
|
||||||
|
|
|
@ -6,11 +6,11 @@ html, body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-family: 'Roboto', sans-serif;
|
font-family: 'Roboto', sans-serif;
|
||||||
background: gainsboro;
|
background: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas {
|
canvas {
|
||||||
padding: 00px;
|
padding: 0px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue