2 * Copyright (c) 2011 Michael Niedermayer
4 * This file is part of FFmpeg.
6 * FFmpeg is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * FFmpeg is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with FFmpeg; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 * The vsrc_color filter from Stefano Sabatini was used as template to create
26 * Mandelbrot fraktal renderer
33 #include "libavutil/imgutils.h"
34 #include "libavutil/opt.h"
35 #include "libavutil/parseutils.h"
39 #define SQR(a) ((a)*(a))
43 NORMALIZED_ITERATION_COUNT
,
55 typedef struct Point
{
63 AVRational frame_rate
;
86 #define OFFSET(x) offsetof(MBContext, x)
87 #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
89 static const AVOption mandelbrot_options
[] = {
90 {"size", "set frame size", OFFSET(w
), AV_OPT_TYPE_IMAGE_SIZE
, {.str
="640x480"}, CHAR_MIN
, CHAR_MAX
, FLAGS
},
91 {"s", "set frame size", OFFSET(w
), AV_OPT_TYPE_IMAGE_SIZE
, {.str
="640x480"}, CHAR_MIN
, CHAR_MAX
, FLAGS
},
92 {"rate", "set frame rate", OFFSET(frame_rate
), AV_OPT_TYPE_VIDEO_RATE
, {.str
="25"}, CHAR_MIN
, CHAR_MAX
, FLAGS
},
93 {"r", "set frame rate", OFFSET(frame_rate
), AV_OPT_TYPE_VIDEO_RATE
, {.str
="25"}, CHAR_MIN
, CHAR_MAX
, FLAGS
},
94 {"maxiter", "set max iterations number", OFFSET(maxiter
), AV_OPT_TYPE_INT
, {.i64
=7189}, 1, INT_MAX
, FLAGS
},
95 {"start_x", "set the initial x position", OFFSET(start_x
), AV_OPT_TYPE_DOUBLE
, {.dbl
=-0.743643887037158704752191506114774}, -100, 100, FLAGS
},
96 {"start_y", "set the initial y position", OFFSET(start_y
), AV_OPT_TYPE_DOUBLE
, {.dbl
=-0.131825904205311970493132056385139}, -100, 100, FLAGS
},
97 {"start_scale", "set the initial scale value", OFFSET(start_scale
), AV_OPT_TYPE_DOUBLE
, {.dbl
=3.0}, 0, FLT_MAX
, FLAGS
},
98 {"end_scale", "set the terminal scale value", OFFSET(end_scale
), AV_OPT_TYPE_DOUBLE
, {.dbl
=0.3}, 0, FLT_MAX
, FLAGS
},
99 {"end_pts", "set the terminal pts value", OFFSET(end_pts
), AV_OPT_TYPE_DOUBLE
, {.dbl
=400}, 0, INT64_MAX
, FLAGS
},
100 {"bailout", "set the bailout value", OFFSET(bailout
), AV_OPT_TYPE_DOUBLE
, {.dbl
=10}, 0, FLT_MAX
, FLAGS
},
101 {"morphxf", "set morph x frequency", OFFSET(morphxf
), AV_OPT_TYPE_DOUBLE
, {.dbl
=0.01}, -FLT_MAX
, FLT_MAX
, FLAGS
},
102 {"morphyf", "set morph y frequency", OFFSET(morphyf
), AV_OPT_TYPE_DOUBLE
, {.dbl
=0.0123}, -FLT_MAX
, FLT_MAX
, FLAGS
},
103 {"morphamp", "set morph amplitude", OFFSET(morphamp
), AV_OPT_TYPE_DOUBLE
, {.dbl
=0}, -FLT_MAX
, FLT_MAX
, FLAGS
},
105 {"outer", "set outer coloring mode", OFFSET(outer
), AV_OPT_TYPE_INT
, {.i64
=NORMALIZED_ITERATION_COUNT
}, 0, INT_MAX
, FLAGS
, "outer" },
106 {"iteration_count", "set iteration count mode", 0, AV_OPT_TYPE_CONST
, {.i64
=ITERATION_COUNT
}, INT_MIN
, INT_MAX
, FLAGS
, "outer" },
107 {"normalized_iteration_count", "set normalized iteration count mode", 0, AV_OPT_TYPE_CONST
, {.i64
=NORMALIZED_ITERATION_COUNT
}, INT_MIN
, INT_MAX
, FLAGS
, "outer" },
108 {"white", "set white mode", 0, AV_OPT_TYPE_CONST
, {.i64
=WHITE
}, INT_MIN
, INT_MAX
, FLAGS
, "outer" },
109 {"outz", "set outz mode", 0, AV_OPT_TYPE_CONST
, {.i64
=OUTZ
}, INT_MIN
, INT_MAX
, FLAGS
, "outer" },
111 {"inner", "set inner coloring mode", OFFSET(inner
), AV_OPT_TYPE_INT
, {.i64
=MINCOL
}, 0, INT_MAX
, FLAGS
, "inner" },
112 {"black", "set black mode", 0, AV_OPT_TYPE_CONST
, {.i64
=BLACK
}, INT_MIN
, INT_MAX
, FLAGS
, "inner"},
113 {"period", "set period mode", 0, AV_OPT_TYPE_CONST
, {.i64
=PERIOD
}, INT_MIN
, INT_MAX
, FLAGS
, "inner"},
114 {"convergence", "show time until convergence", 0, AV_OPT_TYPE_CONST
, {.i64
=CONVTIME
}, INT_MIN
, INT_MAX
, FLAGS
, "inner"},
115 {"mincol", "color based on point closest to the origin of the iterations", 0, AV_OPT_TYPE_CONST
, {.i64
=MINCOL
}, INT_MIN
, INT_MAX
, FLAGS
, "inner"},
120 AVFILTER_DEFINE_CLASS(mandelbrot
);
122 static av_cold
int init(AVFilterContext
*ctx
)
124 MBContext
*mb
= ctx
->priv
;
126 mb
->bailout
*= mb
->bailout
;
128 mb
->start_scale
/=mb
->h
;
129 mb
->end_scale
/=mb
->h
;
131 mb
->cache_allocated
= mb
->w
* mb
->h
* 3;
133 mb
->point_cache
= av_malloc_array(mb
->cache_allocated
, sizeof(*mb
->point_cache
));
134 mb
-> next_cache
= av_malloc_array(mb
->cache_allocated
, sizeof(*mb
-> next_cache
));
135 mb
-> zyklus
= av_malloc_array(mb
->maxiter
+ 16, sizeof(*mb
->zyklus
));
140 static av_cold
void uninit(AVFilterContext
*ctx
)
142 MBContext
*mb
= ctx
->priv
;
144 av_freep(&mb
->point_cache
);
145 av_freep(&mb
-> next_cache
);
146 av_freep(&mb
->zyklus
);
149 static int query_formats(AVFilterContext
*ctx
)
151 static const enum AVPixelFormat pix_fmts
[] = {
156 ff_set_common_formats(ctx
, ff_make_format_list(pix_fmts
));
160 static int config_props(AVFilterLink
*inlink
)
162 AVFilterContext
*ctx
= inlink
->src
;
163 MBContext
*mb
= ctx
->priv
;
165 if (av_image_check_size(mb
->w
, mb
->h
, 0, ctx
) < 0)
166 return AVERROR(EINVAL
);
170 inlink
->time_base
= av_inv_q(mb
->frame_rate
);
175 static void fill_from_cache(AVFilterContext
*ctx
, uint32_t *color
, int *in_cidx
, int *out_cidx
, double py
, double scale
){
176 MBContext
*mb
= ctx
->priv
;
179 for(; *in_cidx
< mb
->cache_used
; (*in_cidx
)++){
180 Point
*p
= &mb
->point_cache
[*in_cidx
];
184 x
= round((p
->p
[0] - mb
->start_x
) / scale
+ mb
->w
/2);
185 if(x
<0 || x
>= mb
->w
)
187 if(color
) color
[x
] = p
->val
;
188 if(out_cidx
&& *out_cidx
< mb
->cache_allocated
)
189 mb
->next_cache
[(*out_cidx
)++]= *p
;
193 static int interpol(MBContext
*mb
, uint32_t *color
, int x
, int y
, int linesize
)
196 uint32_t ipol
=0xFF000000;
199 if(!x
|| !y
|| x
+1==mb
->w
|| y
+1==mb
->h
)
202 dist
= FFMAX(FFABS(x
-(mb
->w
>>1))*mb
->h
, FFABS(y
-(mb
->h
>>1))*mb
->w
);
204 if(dist
<(mb
->w
*mb
->h
>>3))
207 a
=color
[(x
+1) + (y
+0)*linesize
];
208 b
=color
[(x
-1) + (y
+1)*linesize
];
209 c
=color
[(x
+0) + (y
+1)*linesize
];
210 d
=color
[(x
+1) + (y
+1)*linesize
];
213 b
= color
[(x
-1) + (y
+0)*linesize
];
214 d
= color
[(x
+0) + (y
-1)*linesize
];
216 a
= color
[(x
+1) + (y
-1)*linesize
];
217 c
= color
[(x
-1) + (y
-1)*linesize
];
219 d
= color
[(x
+0) + (y
-1)*linesize
];
220 a
= color
[(x
-1) + (y
+0)*linesize
];
221 b
= color
[(x
+1) + (y
-1)*linesize
];
223 c
= color
[(x
-1) + (y
-1)*linesize
];
224 a
= color
[(x
-1) + (y
+0)*linesize
];
225 b
= color
[(x
+1) + (y
-1)*linesize
];
235 int ipolab
= (ac
+ bc
);
236 int ipolcd
= (cc
+ dc
);
237 if(FFABS(ipolab
- ipolcd
) > 5)
239 if(FFABS(ac
-bc
)+FFABS(cc
-dc
) > 20)
241 ipol
|= ((ipolab
+ ipolcd
+ 2)/4)<<s
;
243 color
[x
+ y
*linesize
]= ipol
;
247 static void draw_mandelbrot(AVFilterContext
*ctx
, uint32_t *color
, int linesize
, int64_t pts
)
249 MBContext
*mb
= ctx
->priv
;
250 int x
,y
,i
, in_cidx
=0, next_cidx
=0, tmp_cidx
;
251 double scale
= mb
->start_scale
*pow(mb
->end_scale
/mb
->start_scale
, pts
/mb
->end_pts
);
253 fill_from_cache(ctx
, NULL
, &in_cidx
, NULL
, mb
->start_y
+scale
*(-mb
->h
/2-0.5), scale
);
255 memset(color
, 0, sizeof(*color
)*mb
->w
);
256 for(y
=0; y
<mb
->h
; y
++){
258 const double ci
=mb
->start_y
+scale
*(y
-mb
->h
/2);
259 fill_from_cache(ctx
, NULL
, &in_cidx
, &next_cidx
, ci
, scale
);
261 memset(color
+linesize
*y1
, 0, sizeof(*color
)*mb
->w
);
262 fill_from_cache(ctx
, color
+linesize
*y1
, &tmp_cidx
, NULL
, ci
+ 3*scale
/2, scale
);
265 for(x
=0; x
<mb
->w
; x
++){
266 float av_uninit(epsilon
);
267 const double cr
=mb
->start_x
+scale
*(x
-mb
->w
/2);
271 double dv
= mb
->dither
/ (double)(1LL<<32);
272 mb
->dither
= mb
->dither
*1664525+1013904223;
274 if(color
[x
+ y
*linesize
] & 0xFF000000)
277 if(interpol(mb
, color
, x
, y
, linesize
)){
278 if(next_cidx
< mb
->cache_allocated
){
279 mb
->next_cache
[next_cidx
].p
[0]= cr
;
280 mb
->next_cache
[next_cidx
].p
[1]= ci
;
281 mb
->next_cache
[next_cidx
++].val
= color
[x
+ y
*linesize
];
286 zr
+= cos(pts
* mb
->morphxf
) * mb
->morphamp
;
287 zi
+= sin(pts
* mb
->morphyf
) * mb
->morphamp
;
290 use_zyklus
= (x
==0 || mb
->inner
!=BLACK
||color
[x
-1 + y
*linesize
] == 0xFF000000);
292 epsilon
= scale
*1*sqrt(SQR(x
-mb
->w
/2) + SQR(y
-mb
->h
/2))/mb
->w
;
294 #define Z_Z2_C(outr,outi,inr,ini)\
295 outr= inr*inr - ini*ini + cr;\
296 outi= 2*inr*ini + ci;
298 #define Z_Z2_C_ZYKLUS(outr,outi,inr,ini, Z)\
299 Z_Z2_C(outr,outi,inr,ini)\
301 if(Z && fabs(mb->zyklus[i>>1][0]-outr)+fabs(mb->zyklus[i>>1][1]-outi) <= epsilon)\
304 mb->zyklus[i][0]= outr;\
305 mb->zyklus[i][1]= outi;\
309 for(i=0; i<mb->maxiter-8; i++){
311 Z_Z2_C_ZYKLUS(t
, zi
, zr
, zi
, 0)
313 Z_Z2_C_ZYKLUS(zr
, zi
, t
, zi
, 1)
315 Z_Z2_C_ZYKLUS(t
, zi
, zr
, zi
, 0)
317 Z_Z2_C_ZYKLUS(zr
, zi
, t
, zi
, 1)
319 Z_Z2_C_ZYKLUS(t
, zi
, zr
, zi
, 0)
321 Z_Z2_C_ZYKLUS(zr
, zi
, t
, zi
, 1)
323 Z_Z2_C_ZYKLUS(t
, zi
, zr
, zi
, 0)
325 Z_Z2_C_ZYKLUS(zr
, zi
, t
, zi
, 1)
326 if(zr
*zr
+ zi
*zi
> mb
->bailout
){
328 for(; i
<mb
->maxiter
; i
++){
329 zr
= mb
->zyklus
[i
][0];
330 zi
= mb
->zyklus
[i
][1];
331 if(zr
*zr
+ zi
*zi
> mb
->bailout
){
333 case ITERATION_COUNT
:
335 c
= lrintf((sin(zr
)+1)*127) + lrintf((sin(zr
/1.234)+1)*127)*256*256 + lrintf((sin(zr
/100)+1)*127)*256;
337 case NORMALIZED_ITERATION_COUNT
:
338 zr
= i
+ log2(log(mb
->bailout
) / log(zr
*zr
+ zi
*zi
));
339 c
= lrintf((sin(zr
)+1)*127) + lrintf((sin(zr
/1.234)+1)*127)*256*256 + lrintf((sin(zr
/100)+1)*127)*256;
347 c
= (((int)(zr
*128+128))&0xFF)*256 + (((int)(zi
*128+128))&0xFF);
356 if(mb
->inner
==PERIOD
){
359 if(SQR(mb
->zyklus
[j
][0]-zr
) + SQR(mb
->zyklus
[j
][1]-zi
) < epsilon
*epsilon
*10)
363 c
= ((c
<<5)&0xE0) + ((c
<<10)&0xE000) + ((c
<<15)&0xE00000);
365 }else if(mb
->inner
==CONVTIME
){
366 c
= floor(i
*255.0/mb
->maxiter
+dv
)*0x010101;
367 } else if(mb
->inner
==MINCOL
){
371 for(j
=i
-1; j
>=0; j
--)
372 if(SQR(mb
->zyklus
[j
][0]) + SQR(mb
->zyklus
[j
][1]) < closest
){
373 closest
= SQR(mb
->zyklus
[j
][0]) + SQR(mb
->zyklus
[j
][1]);
376 closest
= sqrt(closest
);
377 c
= lrintf((mb
->zyklus
[closest_index
][0]/closest
+1)*127+dv
) + lrintf((mb
->zyklus
[closest_index
][1]/closest
+1)*127+dv
)*256;
381 color
[x
+ y
*linesize
]= c
;
382 if(next_cidx
< mb
->cache_allocated
){
383 mb
->next_cache
[next_cidx
].p
[0]= cr
;
384 mb
->next_cache
[next_cidx
].p
[1]= ci
;
385 mb
->next_cache
[next_cidx
++].val
= c
;
388 fill_from_cache(ctx
, NULL
, &in_cidx
, &next_cidx
, ci
+ scale
/2, scale
);
390 FFSWAP(void*, mb
->next_cache
, mb
->point_cache
);
391 mb
->cache_used
= next_cidx
;
392 if(mb
->cache_used
== mb
->cache_allocated
)
393 av_log(ctx
, AV_LOG_INFO
, "Mandelbrot cache is too small!\n");
396 static int request_frame(AVFilterLink
*link
)
398 MBContext
*mb
= link
->src
->priv
;
399 AVFrame
*picref
= ff_get_video_buffer(link
, mb
->w
, mb
->h
);
401 return AVERROR(ENOMEM
);
403 picref
->sample_aspect_ratio
= (AVRational
) {1, 1};
404 picref
->pts
= mb
->pts
++;
406 draw_mandelbrot(link
->src
, (uint32_t*)picref
->data
[0], picref
->linesize
[0]/4, picref
->pts
);
407 return ff_filter_frame(link
, picref
);
410 static const AVFilterPad mandelbrot_outputs
[] = {
413 .type
= AVMEDIA_TYPE_VIDEO
,
414 .request_frame
= request_frame
,
415 .config_props
= config_props
,
420 AVFilter ff_vsrc_mandelbrot
= {
421 .name
= "mandelbrot",
422 .description
= NULL_IF_CONFIG_SMALL("Render a Mandelbrot fractal."),
423 .priv_size
= sizeof(MBContext
),
424 .priv_class
= &mandelbrot_class
,
427 .query_formats
= query_formats
,
429 .outputs
= mandelbrot_outputs
,