1 /* === S Y N F I G ========================================================= */
2 /*!	\file palette.cpp
3 **	\brief Template File
4 **
5 **	$Id$
6 **
7 **	\legal
8 **	Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
9 **	Copyright (c) 2010 Nikita Kitaev
10 **	Copyright (c) 2010 Carlos López
11 **
12 **	This package is free software; you can redistribute it and/or
13 **	modify it under the terms of the GNU General Public License as
14 **	published by the Free Software Foundation; either version 2 of
15 **	the License, or (at your option) any later version.
16 **
17 **	This package is distributed in the hope that it will be useful,
18 **	but WITHOUT ANY WARRANTY; without even the implied warranty of
19 **	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 **	General Public License for more details.
21 **	\endlegal
22 */
23 /* ========================================================================= */
24 
25 /* === H E A D E R S ======================================================= */
26 
27 #ifdef USING_PCH
28 #	include "pch.h"
29 #else
30 #ifdef HAVE_CONFIG_H
31 #	include <config.h>
32 #endif
33 
34 #include "palette.h"
35 #include "surface.h"
36 #include "general.h"
37 #include <synfig/localization.h>
38 #include "gamma.h"
39 #include <fstream>
40 #include <iostream>
41 #include <sstream>
42 
43 #endif
44 
45 /* === U S I N G =========================================================== */
46 
47 using namespace std;
48 using namespace etl;
49 using namespace synfig;
50 
51 /* === M A C R O S ========================================================= */
52 
53 #define PALETTE_SYNFIG_FILE_COOKIE	"SYNFIGPAL1.0"
54 #define PALETTE_SYNFIG_EXT ".spal"
55 #define PALETTE_GIMP_FILE_COOKIE "GIMP Palette"
56 #define PALETTE_GIMP_EXT ".gpl"
57 
58 /* === G L O B A L S ======================================================= */
59 
weight_less_than(const PaletteItem & lhs,const PaletteItem & rhs)60 bool weight_less_than(const PaletteItem& lhs,const PaletteItem& rhs)
61 {
62 	return lhs.weight<rhs.weight;
63 }
64 
luma_less_than(const PaletteItem & lhs,const PaletteItem & rhs)65 bool luma_less_than(const PaletteItem& lhs,const PaletteItem& rhs)
66 {
67 	return lhs.color.get_y()<rhs.color.get_y();
68 }
69 
luma_less_than(const PaletteItem & lhs,const float & rhs)70 bool luma_less_than(const PaletteItem& lhs,const float& rhs)
71 {
72 	return lhs.color.get_y()<rhs;
73 }
74 
75 /* === P R O C E D U R E S ================================================= */
76 
77 /* === M E T H O D S ======================================================= */
78 
Palette()79 Palette::Palette():
80 	name_(_("Unnamed"))
81 {
82 }
83 
Palette(const String & name_)84 Palette::Palette(const String& name_):
85 	name_(name_)
86 {
87 }
88 
89 void
add(const Color & x,int xweight)90 PaletteItem::add(const Color& x,int xweight)
91 {
92 	color=(color*weight+x*xweight)/(weight+xweight);
93 	weight+=xweight;
94 }
95 
Palette(const Surface & surface,int max_colors)96 Palette::Palette(const Surface& surface, int max_colors):
97 	name_(_("Surface Palette"))
98 {
99 	max_colors-=2;
100 	for(int i=0;(signed)size()<(max_colors-1) && i<max_colors*16;++i) {
101         int x=rand()%surface.get_w();
102         int y=rand()%surface.get_h();
103 
104 			float dist;
105 			Color color(surface[y][x]);
106 
107 			if(empty())
108 			{
109 				push_back(color);
110 				continue;
111 			}
112 
113 			if(color.get_a()==0)
114 			{
115 				if(front().color.get_a()!=0)
116 					insert(begin(),Color(1,0,1,0));
117 				front().weight+=400;
118 				continue;
119 			}
120 
121 			iterator iter(find_closest(color,&dist));
122 			if(sqrt(dist)<0.005)
123 			{
124 				iter->add(color);
125 				continue;
126 			}
127 
128 			/*if(size()>=max_colors)
129 			{
130 				iterator iterlight(find_light());
131 				PaletteItem light(*iterlight);
132 				erase(iterlight);
133 				find_closest(light.color)->add(light.color,light.weight);
134 			}
135 			*/
136 
137 			push_back(color);
138 			continue;
139     }
140 
141 /*
142 
143 	max_colors-=2;
144 	for(int y=0;y<surface.get_h();y++)
145 		for(int x=0;x<surface.get_w();x++)
146 		{
147 			float dist;
148 			Color color(surface[y][x]);
149 
150 			if(empty())
151 			{
152 				push_back(color);
153 				continue;
154 			}
155 
156 			if(color.get_a()==0)
157 			{
158 				if(front().color.get_a()!=0)
159 					insert(begin(),Color(1,0,1,0));
160 				front().weight+=400;
161 				continue;
162 			}
163 
164 			iterator iter(find_closest(color,&dist));
165 			if(sqrt(dist)<0.005)
166 			{
167 				iter->add(color);
168 				continue;
169 			}
170 
171 
172 			push_back(color);
173 			continue;
174 		}
175 	sort(rbegin(),rend());
176 
177 	iterator iter;
178 
179 	iterator best_match(begin());
180 	while((signed)size()>max_colors)
181 	{
182 		PaletteItem item(back());
183 		pop_back();
184 		find_closest(item.color)->add(item.color,item.weight);
185 	}
186 */
187 	push_back(Color::black());
188 	push_back(Color::white());
189 
190 //	sort(begin(),end(),&luma_less_than);
191 }
192 
193 Palette::const_iterator
find_closest(const Color & color,float * dist) const194 Palette::find_closest(const Color& color, float* dist)const
195 {
196 	// For the sake of avoiding cut-and-paste
197 	// bugs, we'll just use the non-const
198 	// find_closest()... It doesn't change anything
199 	// anyway.
200 	return const_cast<Palette*>(this)->find_closest(color,dist);
201 }
202 
203 Palette::iterator
find_closest(const Color & color,float * dist)204 Palette::find_closest(const Color& color, float* dist)
205 {
206 	iterator iter;
207 
208 	iterator best_match(begin());
209 	float best_dist(1000000);
210 
211 	const float prep_y(powf(color.get_y(),2.2f)*color.get_a());
212 	const float prep_u(color.get_u());
213 	const float prep_v(color.get_v());
214 
215 	for(iter=begin();iter!=end();++iter)
216 	{
217 		const float diff_y(prep_y-powf(iter->color.get_y(),2.2f)*iter->color.get_a());
218 		const float diff_u(prep_u-iter->color.get_u());
219 		const float diff_v(prep_v-iter->color.get_v());
220 		const float diff_a(color.get_a()-iter->color.get_a());
221 
222 
223 		const float dist(
224 			diff_y*diff_y*1.5f+
225 			diff_a*diff_a+
226 
227 			diff_u*diff_u+
228 			diff_v*diff_v
229 
230 			// cross product
231 			/*abs(
232 				prep_u*iter->color.get_u()-
233 				prep_v*iter->color.get_v()
234 			)*/
235 		);
236 		if(dist<best_dist)
237 		{
238 			best_dist=dist;
239 			best_match=iter;
240 		}
241 	}
242 	if(dist)
243 		*dist=best_dist;
244 
245 	return best_match;
246 }
247 
248 
249 Palette::iterator
find_heavy()250 Palette::find_heavy()
251 {
252 	iterator iter;
253 
254 	iterator best_match(begin());
255 
256 	for(iter=begin();iter!=end();++iter)
257 	{
258 		if(iter->weight>best_match->weight)
259 			best_match=iter;
260 	}
261 
262 	return best_match;
263 }
264 
265 Palette::iterator
find_light()266 Palette::find_light()
267 {
268 	iterator iter;
269 
270 	iterator best_match(begin());
271 
272 	for(iter=begin();iter!=end();++iter)
273 	{
274 		if(iter->weight<best_match->weight)
275 			best_match=iter;
276 	}
277 
278 	return best_match;
279 }
280 
281 Palette
grayscale(int steps)282 Palette::grayscale(int steps)
283 {
284 	Palette ret;
285 	for(int i=0;i<steps;i++)
286 	{
287 		float amount(i/(steps-1));
288 		float y(powf(amount,2.2f));
289 		ret.push_back(
290 			PaletteItem(
291 				Color(y,y,y),
292 				strprintf(_("%0.2f%% Gray"),amount)
293 			)
294 		);
295 	}
296 	return ret;
297 }
298 
299 void
save_to_file(const synfig::String & filename) const300 Palette::save_to_file(const synfig::String& filename)const
301 {
302 	const_iterator iter;
303 
304 	std::ofstream file(filename.c_str());
305 
306 	if(!file)
307 		throw strprintf(_("Unable to open %s for write"),filename.c_str());
308 
309 	file<<PALETTE_SYNFIG_FILE_COOKIE<<endl;
310 	file<<name_.c_str()<<endl;
311 	for(iter=begin();iter!=end();++iter)
312 	{
313 		file<<iter->name.c_str()<<endl;
314 		file
315 			<<iter->color.get_r()<<endl
316 			<<iter->color.get_g()<<endl
317 			<<iter->color.get_b()<<endl
318 			<<iter->color.get_a()<<endl;
319 
320 	}
321 }
322 
323 Palette
load_from_file(const synfig::String & filename)324 Palette::load_from_file(const synfig::String& filename)
325 {
326 	std::ifstream file(filename.c_str());
327 
328 	if(!file)
329 		throw strprintf(_("Unable to open %s for read"),filename.c_str());
330 
331 	Palette ret;
332 	String line("");
333 	String ext(filename_extension(filename));
334 
335 
336 	if (ext==PALETTE_SYNFIG_EXT)
337 	{
338 		getline(file,line);
339 
340 		if(line!=PALETTE_SYNFIG_FILE_COOKIE)
341 			throw strprintf(_("%s does not appear to be a valid %s palette file"),filename.c_str(),"Synfig");
342 
343 		getline(file,ret.name_);
344 
345 		while(!file.eof())
346 		{
347 			PaletteItem item;
348 			String n;
349 			float r, g, b, a;
350 			getline(file,item.name);
351 			file >> r >> g >> b >> a;
352 			item.color.set_r(r);
353 			item.color.set_g(g);
354 			item.color.set_b(b);
355 			item.color.set_a(a);
356 
357 			// file ends in new line
358 			if (!file.eof())
359 				ret.push_back(item);
360 		}
361 	}
362 	else if (ext==PALETTE_GIMP_EXT)
363 	{
364 		/*
365 		file format: GPL (GIMP Palette) file should have the following layout:
366 		GIMP Palette
367 		Name: <palette name>
368 		[Columns: <number>]
369 		[#]
370 		[# Optional comments]
371 		[#]
372 		<value R> <value G> <value B> <swatch name>
373 		<value R> <value G> <value B> <swatch name>
374 		... ...
375 		[<new line>]
376 		*/
377 
378 		do {
379 			getline(file,line);
380 		} while (!file.eof() && line != PALETTE_GIMP_FILE_COOKIE);
381 
382 		if (line != PALETTE_GIMP_FILE_COOKIE)
383 			throw strprintf(_("%s does not appear to be a valid %s palette file"),filename.c_str(),"GIMP");
384 
385 
386 		bool has_color = false;
387 
388 		do
389 		{
390 			getline(file, line);
391 
392 			if (!line.empty() && line.substr(0,5) == "Name:")
393 				ret.name_ = String(line.substr(6));
394 			else if (!line.empty() && line.substr(0,8) == "Columns:")
395 				; // Ignore columns
396 			else if (!line.empty() && line.substr(0,1) == "#")
397 				; // Ignore comments
398 			else if (!line.empty())
399 			{
400 				// not empty line not part of the header => color
401 				has_color = true;
402 				// line contains the first color so we put it back in (including \n)
403 				for (int i = line.length()+1; i; i--)
404 					file.unget();
405 			}
406 		} while (!file.eof() && !has_color);
407 
408 		// Gamma color conversion.
409 		// In the importing case, gamma factor is 1, as default
410 		Gamma gamma;
411 
412 		while(!file.eof() && has_color)
413 		{
414 			PaletteItem item;
415 			float r, g, b;
416 
417 			stringstream ss;
418 			getline (file, line);
419 
420 			if (!line.empty())
421 			{
422 				ss << line.c_str();
423 
424 			 	ss >> r >> g >> b;
425 				getline(ss, item.name);
426 
427 				item.color.set_r(gamma.r_F32_to_F32(r/255));
428 				item.color.set_g(gamma.g_F32_to_F32(g/255));
429 				item.color.set_b(gamma.b_F32_to_F32(b/255));
430 				// Alpha is 1 by default
431 				item.color.set_a(1);
432 
433 				ret.push_back(item);
434 			}
435 		}
436 	}
437 	else
438 		throw strprintf(_("%s does not appear to be a supported palette file"),filename.c_str());
439 
440 	return ret;
441 }
442