-
Notifications
You must be signed in to change notification settings - Fork 6
/
index.html
188 lines (159 loc) · 5.74 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
<!DOCTYPE html>
<html>
<head>
<title>Canvas seam carving</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style type="text/css">
#c {
border: none;
margin-top: 5px;
}
h3, img, canvas {
float: left;
}
h3 {
margin-left: 1em;
}
hr {
Visibility: hidden;
clear: both;
}
</style>
</head>
<body><div>
<a href="http://github.com/nicolasff/canvas-seam-carving"><img style="position: absolute; top: 0; right: 0; border: 0;" src="http://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub" /></a>
<h1>Content-Aware Image Resizing using HTML5 <canvas></h1>
<p>
This page is a very basic demo of Content-Aware Image Resizing (CAIR) using <a href="http://en.wikipedia.org/wiki/Seam_Carving">seam carving</a>.<br/>
The demo is very slow, so you can <a href="screenshot.png">see a screenshot of the effect with a 50% resize</a> if you don’t want to wait. There is a lot of room for improvement.<br/>
</p>
<p>
The algorithm is the following:
<ol>
<li>For each pixel from the top row, find the pixel under it that has the closest color (directly under or 1 pixel to the side.)</li>
<li>Add the color distance to a column score.</li>
<li>Repeat until arriving at the bottom row.</li>
<li>Repeat steps 1 – 3 for each column.</li>
<li>Remove the column with the lowest total score.</li>
</ol>
</p>
<button onclick="perform_magic(this, 20);">Click here to remove 20 columns (warning: very slow)</button><hr/>
<img src="image.jpg" id="i" alt="pier"/> <h3>← Original image</h3> <hr />
<img src="image.jpg" id="naive" alt="pier, resized by the browser"/> <h3>← <img> tag, resized by the browser</h3> <hr />
<canvas id="c"></canvas> <h3>← Context-aware resizing in <canvas> (seam carving)</h3> <hr />
<script type="text/javascript">
function Carver(canvasId, url) {
// create canvas & drawing context
this.canvas = document.getElementById(canvasId);
this.context = this.canvas.getContext('2d');
var carver = this;
// load image
var img = new Image();
img.onload = function() {
w = this.width;
h = this.height;
carver.canvas.width = w;
carver.canvas.height = h;
carver.context.drawImage(img, 0, 0, w, h);
carver.w = w;
carver.h = h;
};
img.src = url; // trigger image loading
this.shrink = function() {
// get raw data
var raw = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height);
// difference between two pixels (color vector distance)
var pixel_diff = function(x0,y0, x1,y1) {
var offset0 = 4 * (carver.canvas.width * y0 + x0);
var r0 = raw.data[offset0];
var g0 = raw.data[offset0 + 1];
var b0 = raw.data[offset0 + 2];
var offset1 = 4 * (carver.canvas.width * y1 + x1);
var r1 = raw.data[offset1];
var g1 = raw.data[offset1 + 1];
var b1 = raw.data[offset1 + 2];
return Math.sqrt(Math.pow(r0 - r1, 2) +
Math.pow(g0 - g1, 2) +
Math.pow(b0 - b1, 2));
};
var lowest_score = null;
var lowest_points = null;
for(i = 0; i < this.w; ++i) { // for each column
var score = 0; // column score
var x = i; // starting from i, look around.
var path = new Array(); // storing the points to be removed.
for(j = 0; j < this.h; j++) {
var lots = Math.pow(10, 30);
var delta_left = lots, delta_here = lots, delta_right = lots;
path.push([x, j]);
if(j == this.h - 1) {
break;
}
// comparison between the current pixel and the one under
delta_here = pixel_diff(x, j, x, j+1);
// if there is a pixel on the left, check how different it is.
if(x != 0) {
delta_left = pixel_diff(x, j, x-1, j+1);
}
// if there is a pixel on the right, check how different it is.
if(x != w-1) {
delta_right = pixel_diff(x, j, x+1, j+1);
}
// check which way we're going next.
if(delta_left < delta_here && delta_left < delta_right) {
x--; // go down left
score += delta_left;
} else if(delta_right < delta_here && delta_right < delta_left) {
x++; // go down right
score += delta_right;
} else { // go down
score += delta_here;
}
if(lowest_score != null && score > lowest_score) {
break; // no point continuing
}
}
// save the path with the lowest score
if(lowest_score == null || score < lowest_score) {
lowest_score = score;
lowest_points = path;
}
}
// remove the squiggly “line” of points with the lowest score, row by row.
for(p = 0; p < lowest_points.length; p++) {
var x = lowest_points[p][0];
var y = lowest_points[p][1];
// shifts all pixels on the right of the selected path one position to the left.
for(i = x; i < this.w-1; i++) {
var offset = 4 * (this.canvas.width * y + i);
raw.data[offset] = raw.data[offset+4];
raw.data[offset+1] = raw.data[offset+5];
raw.data[offset+2] = raw.data[offset+6];
raw.data[offset+3] = raw.data[offset+7];
}
// add a single white pixel to the right of the row
offset = 4 * (this.canvas.width * y + this.w-1);
raw.data[offset] = 255;
raw.data[offset+1] = 255;
raw.data[offset+2] = 255;
}
this.context.putImageData(raw, 0, 0);
// now dealing with one less column
this.w--;
// resize the other image block using the browser
var naiveImgResize = document.getElementById("naive");
naiveImgResize.setAttribute("width", this.w);
naiveImgResize.setAttribute("height", this.h);
};
}
var c = new Carver("c", "image.jpg");
function perform_magic(btn, n) {
btn.setAttribute("disabled", "disabled");
for(step = 0; step < n; ++step) {
c.shrink();
}
btn.removeAttribute("disabled");
}
</script>
</div></body>
</html>