brushmask.cpp 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. // Flexlay - A Generic 2D Game Editor
  2. // Copyright (C) 2004 Ingo Ruhnke <grumbel@gmx.de>
  3. //
  4. // This program is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // This program is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. /* Most of the code below is taken from Gimp2.2:
  17. gimp_brush_generated module Copyright 1998 Jay Cox <jaycox@earthlink.net>
  18. */
  19. #include <iostream>
  20. #include <string.h>
  21. #include <ClanLib/Display/pixel_format.h>
  22. #include "brushmask.hpp"
  23. #define OVERSAMPLING 5
  24. typedef int gint;
  25. typedef unsigned char guchar;
  26. struct TempBuf {
  27. gint width;
  28. gint height;
  29. guchar *data; /* The data buffer. Do never access this field
  30. directly, use temp_buf_data() instead !! */
  31. };
  32. void
  33. temp_buf_free (TempBuf *temp_buf)
  34. {
  35. // FIXME:
  36. delete temp_buf;
  37. }
  38. guchar *
  39. temp_buf_data (TempBuf *temp_buf)
  40. {
  41. return temp_buf->data;
  42. }
  43. TempBuf *
  44. temp_buf_new (gint width,
  45. gint height,
  46. gint bytes,
  47. gint x,
  48. gint y,
  49. guchar *col)
  50. {
  51. TempBuf* buf = new TempBuf;
  52. buf->data = new guchar[width*height];
  53. memset(buf->data, 0, width*height*sizeof(guchar));
  54. buf->width = width;
  55. buf->height = height;
  56. return buf;
  57. }
  58. #define gimp_deg_to_rad(angle) ((angle) * (2.0 * M_PI) / 360.0)
  59. #define gimp_rad_to_deg(angle) ((angle) * 360.0 / (2.0 * M_PI))
  60. static double
  61. gauss (double f)
  62. {
  63. /* this aint' a real gauss function */
  64. if (f < -0.5)
  65. {
  66. f = -1.0 - f;
  67. return (2.0 * f*f);
  68. }
  69. if (f < 0.5)
  70. return (1.0 - 2.0 * f*f);
  71. f = 1.0 - f;
  72. return (2.0 * f*f);
  73. }
  74. struct GimpVector2
  75. {
  76. double x, y;
  77. };
  78. struct GimpBrushGenerated
  79. {
  80. TempBuf *mask; /* the actual mask */
  81. GimpVector2 x_axis; /* for calculating brush spacing */
  82. GimpVector2 y_axis; /* for calculating brush spacing */
  83. BrushShape shape;
  84. float radius;
  85. gint spikes; /* 2 - 20 */
  86. float hardness; /* 0.0 - 1.0 */
  87. float aspect_ratio; /* y/x */
  88. float angle; /* in degrees */
  89. };
  90. static void
  91. gimp_brush_generated_dirty (GimpBrushGenerated *brush)
  92. {
  93. gint x, y;
  94. guchar *centerp;
  95. double d;
  96. double exponent;
  97. guchar a;
  98. gint length;
  99. gint width = 0;
  100. gint height = 0;
  101. guchar *lookup;
  102. double sum;
  103. double c, s, cs, ss;
  104. double short_radius;
  105. double buffer[OVERSAMPLING];
  106. if (brush->mask)
  107. temp_buf_free (brush->mask);
  108. s = sin (gimp_deg_to_rad (brush->angle));
  109. c = cos (gimp_deg_to_rad (brush->angle));
  110. short_radius = brush->radius / brush->aspect_ratio;
  111. brush->x_axis.x = c * brush->radius;
  112. brush->x_axis.y = -1.0 * s * brush->radius;
  113. brush->y_axis.x = s * short_radius;
  114. brush->y_axis.y = c * short_radius;
  115. switch (brush->shape)
  116. {
  117. case BRUSH_SHAPE_CIRCLE:
  118. width = static_cast<int>(ceil (sqrt (brush->x_axis.x * brush->x_axis.x +
  119. brush->y_axis.x * brush->y_axis.x)));
  120. height = static_cast<int>(ceil (sqrt (brush->x_axis.y * brush->x_axis.y +
  121. brush->y_axis.y * brush->y_axis.y)));
  122. break;
  123. case BRUSH_SHAPE_SQUARE:
  124. width = static_cast<int>(ceil (fabs (brush->x_axis.x) + fabs (brush->y_axis.x)));
  125. height = static_cast<int>(ceil (fabs (brush->x_axis.y) + fabs (brush->y_axis.y)));
  126. break;
  127. case BRUSH_SHAPE_DIAMOND:
  128. width = static_cast<int>(ceil (std::max(fabs (brush->x_axis.x), fabs (brush->y_axis.x))));
  129. height = static_cast<int>(ceil (std::max(fabs (brush->x_axis.y), fabs (brush->y_axis.y))));
  130. break;
  131. default:
  132. return;
  133. }
  134. if (brush->spikes > 2)
  135. {
  136. /* could be optimized by respecting the angle */
  137. width = height = static_cast<int>(ceil (sqrt (brush->radius * brush->radius +
  138. short_radius * short_radius)));
  139. brush->y_axis.x = s * brush->radius;
  140. brush->y_axis.y = c * brush->radius;
  141. }
  142. brush->mask = temp_buf_new (width * 2 + 1,
  143. height * 2 + 1,
  144. 1, width, height, NULL);
  145. centerp = temp_buf_data (brush->mask) + height * brush->mask->width + width;
  146. /* set up lookup table */
  147. length = static_cast<int>(OVERSAMPLING * ceil (1 + sqrt (2 *
  148. ceil (brush->radius + 1.0) *
  149. ceil (brush->radius + 1.0))));
  150. if ((1.0 - brush->hardness) < 0.0000004)
  151. exponent = 1000000.0;
  152. else
  153. exponent = 0.4 / (1.0 - brush->hardness);
  154. lookup = new guchar[length];
  155. sum = 0.0;
  156. for (x = 0; x < OVERSAMPLING; x++)
  157. {
  158. d = fabs ((x + 0.5) / OVERSAMPLING - 0.5);
  159. if (d > brush->radius)
  160. buffer[x] = 0.0;
  161. else
  162. buffer[x] = gauss (pow (d / brush->radius, exponent));
  163. sum += buffer[x];
  164. }
  165. for (x = 0; d < brush->radius || sum > 0.00001; d += 1.0 / OVERSAMPLING)
  166. {
  167. sum -= buffer[x % OVERSAMPLING];
  168. if (d > brush->radius)
  169. buffer[x % OVERSAMPLING] = 0.0;
  170. else
  171. buffer[x % OVERSAMPLING] = gauss (pow (d / brush->radius, exponent));
  172. sum += buffer[x % OVERSAMPLING];
  173. lookup[x++] = static_cast<int>(rint(sum * (255.0 / OVERSAMPLING)));
  174. }
  175. while (x < length)
  176. {
  177. lookup[x++] = 0;
  178. }
  179. cs = cos (- 2 * M_PI / brush->spikes);
  180. ss = sin (- 2 * M_PI / brush->spikes);
  181. /* for an even number of spikes compute one half and mirror it */
  182. for (y = (brush->spikes % 2 ? -height : 0); y <= height; y++)
  183. {
  184. for (x = -width; x <= width; x++)
  185. {
  186. double tx, ty, angle;
  187. tx = c*x - s*y;
  188. ty = fabs (s*x + c*y);
  189. if (brush->spikes > 2)
  190. {
  191. angle = atan2 (ty, tx);
  192. while (angle > M_PI / brush->spikes)
  193. {
  194. double sx = tx, sy = ty;
  195. tx = cs * sx - ss * sy;
  196. ty = ss * sx + cs * sy;
  197. angle -= 2 * M_PI / brush->spikes;
  198. }
  199. }
  200. ty *= brush->aspect_ratio;
  201. switch (brush->shape)
  202. {
  203. case BRUSH_SHAPE_CIRCLE:
  204. d = sqrt (tx*tx + ty*ty);
  205. break;
  206. case BRUSH_SHAPE_SQUARE:
  207. d = std::max (fabs (tx), fabs (ty));
  208. break;
  209. case BRUSH_SHAPE_DIAMOND:
  210. d = fabs (tx) + fabs (ty);
  211. break;
  212. }
  213. if (d < brush->radius + 1)
  214. a = lookup[(gint) rint (d * OVERSAMPLING)];
  215. else
  216. a = 0;
  217. centerp[ y * brush->mask->width + x] = a;
  218. if (brush->spikes % 2 == 0)
  219. centerp[-1 * y * brush->mask->width - x] = a;
  220. }
  221. }
  222. delete lookup;
  223. }
  224. CL_PixelBuffer generate_brushmask(BrushShape shape,
  225. float radius,
  226. int spikes, /* 2 - 20 */
  227. float hardness, /* 0.0 - 1.0 */
  228. float aspect_ratio, /* y/x */
  229. float angle) /* in degrees */
  230. {
  231. GimpBrushGenerated brush;
  232. brush.mask = 0;
  233. brush.shape = shape;
  234. brush.radius = radius;
  235. brush.spikes = spikes;
  236. brush.hardness = hardness;
  237. brush.aspect_ratio = aspect_ratio;
  238. brush.angle = angle;
  239. gimp_brush_generated_dirty(&brush);
  240. CL_PixelBuffer buffer(brush.mask->width, brush.mask->height, brush.mask->width*4,
  241. CL_PixelFormat::rgba8888);
  242. buffer.lock();
  243. unsigned char* buf = static_cast<unsigned char*>(buffer.get_data());
  244. // FIXME: Leaving out the right/bottom border, since thats full of
  245. // random spots... more a workaround than a fix really
  246. for (int i = 0; i < brush.mask->height * brush.mask->width; ++i)
  247. {
  248. buf[i*4+0] = brush.mask->data[i];
  249. buf[i*4+1] = 255;
  250. buf[i*4+2] = 255;
  251. buf[i*4+3] = 255;
  252. }
  253. buffer.unlock();
  254. return buffer;
  255. }
  256. #ifdef TEST
  257. int main()
  258. {
  259. GimpBrushGenerated brush;
  260. brush.mask = 0;
  261. brush.shape = BRUSH_SHAPE_DIAMOND;
  262. brush.radius = 512;
  263. brush.spikes = 19;
  264. brush.hardness = 0.9;
  265. brush.aspect_ratio = 1;
  266. brush.angle = 0;
  267. gimp_brush_generated_dirty(&brush);
  268. std::cout << "P2\n";
  269. std::cout << "# Gimp Brush Generator\n";
  270. std::cout << brush.mask->width << " " << brush.mask->height << "\n";
  271. std::cout << "255\n";
  272. for (int i = 0; i < brush.mask->width * brush.mask->height; ++i)
  273. std::cout << int(brush.mask->data[i]) << " ";
  274. temp_buf_free(brush.mask);
  275. std::cout << std::endl;
  276. return 0;
  277. }
  278. #endif
  279. /* EOF */