1997-11-25 06:05:25 +08:00
/* The GIMP -- an image manipulation program
* Copyright ( C ) 1995 Spencer Kimball and Peter Mattis
*
* Polarize plug - in - - - maps a rectangul to a circle or vice - versa
* Copyright ( C ) 1997 Daniel Dunbar
* Email : ddunbar @ diads . com
* WWW : http : //millennium.diads.com/gimp/
* Copyright ( C ) 1997 Federico Mena Quintero
* federico @ nuclecu . unam . mx
* Copyright ( C ) 1996 Marc Bless
* E - mail : bless @ ai - lab . fh - furtwangen . de
* WWW : www . ai - lab . fh - furtwangen . de / ~ bless
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
1998-02-20 18:41:20 +08:00
/* Version 1.0:
* This is the follow - up release . It contains a few minor changes , the
* most major being that the first time I released the wrong version of
* the code , and this time the changes have been fixed . I also added
* tooltips to the dialog .
*
* Feel free to email me if you have any comments or suggestions on this
* plugin .
* - - Daniel Dunbar
* ddunbar @ diads . com
*/
1997-11-25 06:05:25 +08:00
/* Version .5:
* This is the first version publicly released , it will probably be the
* last also unless i can think of some features i want to add .
*
* This plug - in was created with loads of help from quartic ( Frederico
* Mena Quintero ) , and would surely not have come about without it .
*
* The polar algorithms is copied from Marc Bless ' polar plug - in for
* .54 , many thanks to him also .
*
* If you can think of a neat addition to this plug - in , or any other
* info about it , please email me at ddunbar @ diads . com .
* - Daniel Dunbar
*/
# include <math.h>
# include <signal.h>
# include <stdlib.h>
# include <unistd.h>
# include "gtk/gtk.h"
# include "libgimp/gimp.h"
1998-03-19 19:30:56 +08:00
# ifndef M_PI
# define M_PI 3.14159265358979323846
# endif /* M_PI */
1997-11-25 06:05:25 +08:00
# define sqr(x) ((x) * (x))
# define WITHIN(a, b, c) ((((a) <= (b)) && ((b) <= (c))) ? 1 : 0)
/***** Magic numbers *****/
# define PLUG_IN_NAME "plug_in_polar_coords"
# define PLUG_IN_VERSION "July 1997, 0.5"
# define PREVIEW_SIZE 128
# define SCALE_WIDTH 200
# define ENTRY_WIDTH 60
# define CHECK_SIZE 8
# define CHECK_DARK ((int) (1.0 / 3.0 * 255))
# define CHECK_LIGHT ((int) (2.0 / 3.0 * 255))
/***** Types *****/
typedef struct {
gdouble circle ;
gdouble angle ;
1998-02-20 18:41:20 +08:00
gint backwards ;
gint inverse ;
gint polrec ;
1997-11-25 06:05:25 +08:00
} polarize_vals_t ;
typedef struct {
GtkWidget * preview ;
guchar * check_row_0 ;
guchar * check_row_1 ;
guchar * image ;
guchar * dimage ;
gint run ;
} polarize_interface_t ;
typedef struct {
gint col , row ;
gint img_width , img_height , img_bpp , img_has_alpha ;
gint tile_width , tile_height ;
guchar bg_color [ 4 ] ;
GDrawable * drawable ;
GTile * tile ;
} pixel_fetcher_t ;
/***** Prototypes *****/
static void query ( void ) ;
static void run ( char * name ,
int nparams ,
GParam * param ,
int * nreturn_vals ,
GParam * * return_vals ) ;
static void polarize ( void ) ;
static int calc_undistorted_coords ( double wx , double wy ,
double * x , double * y ) ;
static guchar bilinear ( double x , double y , guchar * values ) ;
static pixel_fetcher_t * pixel_fetcher_new ( GDrawable * drawable ) ;
static void pixel_fetcher_set_bg_color ( pixel_fetcher_t * pf , guchar r , guchar g , guchar b , guchar a ) ;
static void pixel_fetcher_get_pixel ( pixel_fetcher_t * pf , int x , int y , guchar * pixel ) ;
static void pixel_fetcher_destroy ( pixel_fetcher_t * pf ) ;
static void build_preview_source_image ( void ) ;
static gint polarize_dialog ( void ) ;
static void dialog_update_preview ( void ) ;
static void dialog_create_value ( char * title , GtkTable * table , int row , gdouble * value ,
double left , double right , double step ) ;
static void dialog_scale_update ( GtkAdjustment * adjustment , gdouble * value ) ;
static void dialog_entry_update ( GtkWidget * widget , gdouble * value ) ;
static void dialog_close_callback ( GtkWidget * widget , gpointer data ) ;
static void dialog_ok_callback ( GtkWidget * widget , gpointer data ) ;
static void dialog_cancel_callback ( GtkWidget * widget , gpointer data ) ;
1998-02-20 18:41:20 +08:00
static void polar_toggle_callback ( GtkWidget * widget , gpointer data ) ;
static void set_tooltip ( GtkTooltips * tooltips , GtkWidget * widget , const char * desc ) ;
1997-11-25 06:05:25 +08:00
/***** Variables *****/
GPlugInInfo PLUG_IN_INFO = {
NULL , /* init_proc */
NULL , /* quit_proc */
query , /* query_proc */
run /* run_proc */
} ; /* PLUG_IN_INFO */
1998-02-20 18:41:20 +08:00
static polarize_vals_t pcvals = {
1997-11-25 06:05:25 +08:00
100.0 , /* circle */
0.0 , /* angle */
0 , /* backwards */
1 , /* inverse */
1 /* polar to rectangular? */
1998-02-20 18:41:20 +08:00
} ; /* pcvals */
1997-11-25 06:05:25 +08:00
1998-02-20 18:41:20 +08:00
static polarize_interface_t pcint = {
1997-11-25 06:05:25 +08:00
NULL , /* preview */
NULL , /* check_row_0 */
NULL , /* check_row_1 */
NULL , /* image */
NULL , /* dimage */
FALSE /* run */
1998-02-20 18:41:20 +08:00
} ; /* pcint */
1997-11-25 06:05:25 +08:00
static GDrawable * drawable ;
static gint img_width , img_height , img_bpp , img_has_alpha ;
static gint sel_x1 , sel_y1 , sel_x2 , sel_y2 ;
static gint sel_width , sel_height ;
static gint preview_width , preview_height ;
static double cen_x , cen_y ;
static double scale_x , scale_y ;
/***** Functions *****/
/*****/
MAIN ( )
/*****/
static void
query ( void )
{
static GParamDef args [ ] = {
{ PARAM_INT32 , " run_mode " , " Interactive, non-interactive " } ,
{ PARAM_IMAGE , " image " , " Input image " } ,
{ PARAM_DRAWABLE , " drawable " , " Input drawable " } ,
{ PARAM_FLOAT , " circle " , " Circle depth in % " } ,
{ PARAM_FLOAT , " angle " , " Offset angle " } ,
1998-03-19 19:30:56 +08:00
{ PARAM_INT32 , " backwards " , " Map backwards? " } ,
{ PARAM_INT32 , " inverse " , " Map from top? " } ,
{ PARAM_INT32 , " polrec " , " Polar to rectangular? " } ,
1997-11-25 06:05:25 +08:00
} ; /* args */
static GParamDef * return_vals = NULL ;
static int nargs = sizeof ( args ) / sizeof ( args [ 0 ] ) ;
static int nreturn_vals = 0 ;
gimp_install_procedure ( PLUG_IN_NAME ,
" Converts and image to and from polar coords " ,
" Remaps and image from rectangular coordinates to polar coordinats or vice versa " ,
" Daniel Dunbar and Federico Mena Quintero " ,
" Daniel Dunbar and Federico Mena Quintero " ,
PLUG_IN_VERSION ,
" <Image>/Filters/Distorts/Polar Coords " ,
" RGB*, GRAY* " ,
PROC_PLUG_IN ,
nargs ,
nreturn_vals ,
args ,
return_vals ) ;
} /* query */
/*****/
static void
run ( char * name ,
int nparams ,
GParam * param ,
int * nreturn_vals ,
GParam * * return_vals )
{
static GParam values [ 1 ] ;
GRunModeType run_mode ;
GStatusType status ;
double xhsiz , yhsiz ;
int pwidth , pheight ;
status = STATUS_SUCCESS ;
run_mode = param [ 0 ] . data . d_int32 ;
values [ 0 ] . type = PARAM_STATUS ;
values [ 0 ] . data . d_status = status ;
* nreturn_vals = 1 ;
* return_vals = values ;
/* Get the active drawable info */
drawable = gimp_drawable_get ( param [ 2 ] . data . d_drawable ) ;
img_width = gimp_drawable_width ( drawable - > id ) ;
img_height = gimp_drawable_height ( drawable - > id ) ;
img_bpp = gimp_drawable_bpp ( drawable - > id ) ;
img_has_alpha = gimp_drawable_has_alpha ( drawable - > id ) ;
gimp_drawable_mask_bounds ( drawable - > id , & sel_x1 , & sel_y1 , & sel_x2 , & sel_y2 ) ;
/* Calculate scaling parameters */
sel_width = sel_x2 - sel_x1 ;
sel_height = sel_y2 - sel_y1 ;
cen_x = ( double ) ( sel_x1 + sel_x2 - 1 ) / 2.0 ;
cen_y = ( double ) ( sel_y1 + sel_y2 - 1 ) / 2.0 ;
xhsiz = ( double ) ( sel_width - 1 ) / 2.0 ;
yhsiz = ( double ) ( sel_height - 1 ) / 2.0 ;
if ( xhsiz < yhsiz ) {
scale_x = yhsiz / xhsiz ;
scale_y = 1.0 ;
} else if ( xhsiz > yhsiz ) {
scale_x = 1.0 ;
scale_y = xhsiz / yhsiz ;
} else {
scale_x = 1.0 ;
scale_y = 1.0 ;
} /* else */
/* Calculate preview size */
if ( sel_width > sel_height ) {
pwidth = MIN ( sel_width , PREVIEW_SIZE ) ;
pheight = sel_height * pwidth / sel_width ;
} else {
pheight = MIN ( sel_height , PREVIEW_SIZE ) ;
pwidth = sel_width * pheight / sel_height ;
} /* else */
preview_width = MAX ( pwidth , 2 ) ; /* Min size is 2 */
preview_height = MAX ( pheight , 2 ) ;
/* See how we will run */
switch ( run_mode ) {
case RUN_INTERACTIVE :
/* Possibly retrieve data */
1998-02-20 18:41:20 +08:00
gimp_get_data ( PLUG_IN_NAME , & pcvals ) ;
1997-11-25 06:05:25 +08:00
/* Get information from the dialog */
if ( ! polarize_dialog ( ) )
return ;
break ;
case RUN_NONINTERACTIVE :
/* Make sure all the arguments are present */
if ( nparams ! = 8 )
status = STATUS_CALLING_ERROR ;
if ( status = = STATUS_SUCCESS ) {
1998-02-20 18:41:20 +08:00
pcvals . circle = param [ 3 ] . data . d_float ;
pcvals . angle = param [ 4 ] . data . d_float ;
1998-03-19 19:30:56 +08:00
pcvals . backwards = param [ 5 ] . data . d_int32 ;
pcvals . inverse = param [ 6 ] . data . d_int32 ;
pcvals . polrec = param [ 7 ] . data . d_int32 ;
1997-11-25 06:05:25 +08:00
} /* if */
break ;
case RUN_WITH_LAST_VALS :
/* Possibly retrieve data */
1998-02-20 18:41:20 +08:00
gimp_get_data ( PLUG_IN_NAME , & pcvals ) ;
1997-11-25 06:05:25 +08:00
break ;
default :
break ;
} /* switch */
/* Distort the image */
if ( ( status = = STATUS_SUCCESS ) & &
( gimp_drawable_color ( drawable - > id ) | |
gimp_drawable_gray ( drawable - > id ) ) ) {
/* Set the tile cache size */
gimp_tile_cache_ntiles ( 2 * ( drawable - > width + gimp_tile_width ( ) - 1 ) / gimp_tile_width ( ) ) ;
/* Run! */
polarize ( ) ;
/* If run mode is interactive, flush displays */
if ( run_mode ! = RUN_NONINTERACTIVE )
gimp_displays_flush ( ) ;
/* Store data */
if ( run_mode = = RUN_INTERACTIVE )
1998-02-20 18:41:20 +08:00
gimp_set_data ( PLUG_IN_NAME , & pcvals , sizeof ( polarize_vals_t ) ) ;
1997-11-25 06:05:25 +08:00
} else if ( status = = STATUS_SUCCESS )
status = STATUS_EXECUTION_ERROR ;
values [ 0 ] . data . d_status = status ;
gimp_drawable_detach ( drawable ) ;
} /* run */
/*****/
static void
polarize ( void ) {
GPixelRgn dest_rgn ;
guchar * dest , * d ;
guchar pixel [ 4 ] [ 4 ] ;
guchar pixel2 [ 4 ] ;
guchar values [ 4 ] ;
gint progress , max_progress ;
double cx , cy ;
guchar bg_color [ 4 ] ;
gint x1 , y1 , x2 , y2 ;
gint x , y , b ;
gpointer pr ;
pixel_fetcher_t * pft ;
#if 0
printf ( " Waiting... (pid %d) \n " , getpid ( ) ) ;
kill ( getpid ( ) , SIGSTOP ) ;
# endif
/* Get selection area */
gimp_drawable_mask_bounds ( drawable - > id , & x1 , & y1 , & x2 , & y2 ) ;
/* Initialize pixel region */
gimp_pixel_rgn_init ( & dest_rgn , drawable , x1 , y1 , ( x2 - x1 ) , ( y2 - y1 ) , TRUE , TRUE ) ;
pft = pixel_fetcher_new ( drawable ) ;
gimp_palette_get_background ( & bg_color [ 0 ] , & bg_color [ 1 ] , & bg_color [ 2 ] ) ;
pixel_fetcher_set_bg_color ( pft , bg_color [ 0 ] , bg_color [ 1 ] , bg_color [ 2 ] , ( img_has_alpha ? 0 : 255 ) ) ;
progress = 0 ;
max_progress = img_width * img_height ;
gimp_progress_init ( " Polarizing... " ) ;
for ( pr = gimp_pixel_rgns_register ( 1 , & dest_rgn ) ; pr ! = NULL ; pr = gimp_pixel_rgns_process ( pr ) ) {
dest = dest_rgn . data ;
for ( y = dest_rgn . y ; y < ( dest_rgn . y + dest_rgn . h ) ; y + + ) {
d = dest ;
for ( x = dest_rgn . x ; x < ( dest_rgn . x + dest_rgn . w ) ; x + + ) {
if ( calc_undistorted_coords ( x , y , & cx , & cy ) ) {
pixel_fetcher_get_pixel ( pft , cx , cy , pixel [ 0 ] ) ;
pixel_fetcher_get_pixel ( pft , cx + 1 , cy , pixel [ 1 ] ) ;
pixel_fetcher_get_pixel ( pft , cx , cy + 1 , pixel [ 2 ] ) ;
pixel_fetcher_get_pixel ( pft , cx + 1 , cy + 1 , pixel [ 3 ] ) ;
for ( b = 0 ; b < img_bpp ; b + + ) {
values [ 0 ] = pixel [ 0 ] [ b ] ;
values [ 1 ] = pixel [ 1 ] [ b ] ;
values [ 2 ] = pixel [ 2 ] [ b ] ;
values [ 3 ] = pixel [ 3 ] [ b ] ;
d [ b ] = bilinear ( cx , cy , values ) ;
}
} else {
pixel_fetcher_get_pixel ( pft , x , y , pixel2 ) ;
for ( b = 0 ; b < img_bpp ; b + + ) {
d [ b ] = 255 ;
}
}
d + = dest_rgn . bpp ;
}
dest + = dest_rgn . rowstride ;
}
progress + = dest_rgn . w * dest_rgn . h ;
gimp_progress_update ( ( double ) progress / max_progress ) ;
}
gimp_drawable_flush ( drawable ) ;
gimp_drawable_merge_shadow ( drawable - > id , TRUE ) ;
gimp_drawable_update ( drawable - > id , x1 , y1 , ( x2 - x1 ) , ( y2 - y1 ) ) ;
} /* polarize */
/*****/
static int
calc_undistorted_coords ( double wx , double wy ,
double * x , double * y )
{
int inside ;
double phi , phi2 ;
double xx , xm , ym , yy ;
int xdiff , ydiff ;
double r ;
double m ;
double xmax , ymax , rmax ;
double x_calc , y_calc ;
double xi , yi ;
double circle , angl , t , angle ;
int x1 , x2 , y1 , y2 ;
/* initialize */
phi = 0.0 ;
r = 0.0 ;
x1 = 0 ;
y1 = 0 ;
x2 = img_width ;
y2 = img_height ;
xdiff = x2 - x1 ;
ydiff = y2 - y1 ;
xm = xdiff / 2.0 ;
ym = ydiff / 2.0 ;
1998-02-20 18:41:20 +08:00
circle = pcvals . circle ;
angle = pcvals . angle ;
1997-11-25 06:05:25 +08:00
angl = ( double ) angle / 180.0 * M_PI ;
1998-02-20 18:41:20 +08:00
if ( pcvals . polrec ) {
1997-11-25 06:05:25 +08:00
if ( wx > = cen_x ) {
if ( wy > cen_y ) {
phi = M_PI - atan ( ( ( double ) ( wx - cen_x ) ) / ( ( double ) ( wy - cen_y ) ) ) ;
r = sqrt ( sqr ( wx - cen_x ) + sqr ( wy - cen_y ) ) ;
} else if ( wy < cen_y ) {
phi = atan ( ( ( double ) ( wx - cen_x ) ) / ( ( double ) ( cen_y - wy ) ) ) ;
r = sqrt ( sqr ( wx - cen_x ) + sqr ( cen_y - wy ) ) ;
} else {
phi = M_PI / 2 ;
r = wx - cen_x ; /* cen_x - x1; */
}
} else if ( wx < cen_x ) {
if ( wy < cen_y ) {
phi = 2 * M_PI - atan ( ( ( double ) ( cen_x - wx ) ) / ( ( double ) ( cen_y - wy ) ) ) ;
r = sqrt ( sqr ( cen_x - wx ) + sqr ( cen_y - wy ) ) ;
} else if ( wy > cen_y ) {
phi = M_PI + atan ( ( ( double ) ( cen_x - wx ) ) / ( ( double ) ( wy - cen_y ) ) ) ;
r = sqrt ( sqr ( cen_x - wx ) + sqr ( wy - cen_y ) ) ;
} else {
phi = 1.5 * M_PI ;
r = cen_x - wx ; /* cen_x - x1; */
}
}
if ( wx ! = cen_x ) {
m = fabs ( ( ( double ) ( wy - cen_y ) ) / ( ( double ) ( wx - cen_x ) ) ) ;
} else {
m = 0 ;
}
if ( m < = ( ( double ) ( y2 - y1 ) / ( double ) ( x2 - x1 ) ) ) {
if ( wx = = cen_x ) {
xmax = 0 ;
ymax = cen_y - y1 ;
} else {
xmax = cen_x - x1 ;
ymax = m * xmax ;
}
} else {
ymax = cen_y - y1 ;
xmax = ymax / m ;
}
rmax = sqrt ( ( double ) ( sqr ( xmax ) + sqr ( ymax ) ) ) ;
t = ( ( cen_y - y1 ) < ( cen_x - x1 ) ) ? ( cen_y - y1 ) : ( cen_x - x1 ) ;
rmax = ( rmax - t ) / 100 * ( 100 - circle ) + t ;
phi = fmod ( phi + angl , 2 * M_PI ) ;
1998-02-20 18:41:20 +08:00
if ( pcvals . backwards )
1997-11-25 06:05:25 +08:00
x_calc = x2 - 1 - ( x2 - x1 - 1 ) / ( 2 * M_PI ) * phi ;
else
x_calc = ( x2 - x1 - 1 ) / ( 2 * M_PI ) * phi + x1 ;
1998-02-20 18:41:20 +08:00
if ( pcvals . inverse )
1997-11-25 06:05:25 +08:00
y_calc = ( y2 - y1 ) / rmax * r + y1 ;
else
y_calc = y2 - ( y2 - y1 ) / rmax * r ;
xi = ( int ) ( x_calc + 0.5 ) ;
yi = ( int ) ( y_calc + 0.5 ) ;
if ( WITHIN ( 0 , xi , img_width - 1 ) & & WITHIN ( 0 , yi , img_height - 1 ) ) {
* x = x_calc ;
* y = y_calc ;
inside = TRUE ;
} else {
inside = FALSE ;
}
} else {
1998-02-20 18:41:20 +08:00
if ( pcvals . backwards )
1997-11-25 06:05:25 +08:00
phi = ( 2 * M_PI ) * ( x2 - wx ) / xdiff ;
else
phi = ( 2 * M_PI ) * ( wx - x1 ) / xdiff ;
phi = fmod ( phi + angl , 2 * M_PI ) ;
if ( phi > = 1.5 * M_PI )
phi2 = 2 * M_PI - phi ;
else
if ( phi > = M_PI )
phi2 = phi - M_PI ;
else
if ( phi > = 0.5 * M_PI )
phi2 = M_PI - phi ;
else
phi2 = phi ;
xx = tan ( phi2 ) ;
if ( xx ! = 0 )
m = ( double ) 1.0 / xx ;
else
m = 0 ;
if ( m < = ( ( double ) ( ydiff ) / ( double ) ( xdiff ) ) )
{
if ( phi2 = = 0 )
{
xmax = 0 ;
ymax = ym - y1 ;
}
else
{
xmax = xm - x1 ;
ymax = m * xmax ;
}
}
else
{
ymax = ym - y1 ;
xmax = ymax / m ;
}
rmax = sqrt ( ( double ) ( sqr ( xmax ) + sqr ( ymax ) ) ) ;
t = ( ( ym - y1 ) < ( xm - x1 ) ) ? ( ym - y1 ) : ( xm - x1 ) ;
rmax = ( rmax - t ) / 100.0 * ( 100 - circle ) + t ;
1998-02-20 18:41:20 +08:00
if ( pcvals . inverse )
1997-11-25 06:05:25 +08:00
r = rmax * ( double ) ( ( wy - y1 ) / ( double ) ( ydiff ) ) ;
else
r = rmax * ( double ) ( ( y2 - wy ) / ( double ) ( ydiff ) ) ;
xx = r * sin ( phi2 ) ;
yy = r * cos ( phi2 ) ;
if ( phi > = 1.5 * M_PI )
{
x_calc = ( double ) xm - xx ;
y_calc = ( double ) ym - yy ;
}
else
if ( phi > = M_PI )
{
x_calc = ( double ) xm - xx ;
y_calc = ( double ) ym + yy ;
}
else
if ( phi > = 0.5 * M_PI )
{
x_calc = ( double ) xm + xx ;
y_calc = ( double ) ym + yy ;
}
else
{
x_calc = ( double ) xm + xx ;
y_calc = ( double ) ym - yy ;
}
xi = ( int ) ( x_calc + 0.5 ) ;
yi = ( int ) ( y_calc + 0.5 ) ;
if ( WITHIN ( 0 , xi , img_width - 1 ) & & WITHIN ( 0 , yi , img_height - 1 ) ) {
* x = x_calc ;
* y = y_calc ;
inside = TRUE ;
} else {
inside = FALSE ;
}
}
return inside ;
}
/* calc_undistorted_coords */
/*****/
static guchar
bilinear ( double x , double y , guchar * values )
{
double m0 , m1 ;
x = fmod ( x , 1.0 ) ;
y = fmod ( y , 1.0 ) ;
if ( x < 0.0 )
x + = 1.0 ;
if ( y < 0.0 )
y + = 1.0 ;
m0 = ( double ) values [ 0 ] + x * ( ( double ) values [ 1 ] - values [ 0 ] ) ;
m1 = ( double ) values [ 2 ] + x * ( ( double ) values [ 3 ] - values [ 2 ] ) ;
return ( guchar ) ( m0 + y * ( m1 - m0 ) ) ;
} /* bilinear */
/*****/
static pixel_fetcher_t *
pixel_fetcher_new ( GDrawable * drawable )
{
pixel_fetcher_t * pf ;
pf = g_malloc ( sizeof ( pixel_fetcher_t ) ) ;
pf - > col = - 1 ;
pf - > row = - 1 ;
pf - > img_width = gimp_drawable_width ( drawable - > id ) ;
pf - > img_height = gimp_drawable_height ( drawable - > id ) ;
pf - > img_bpp = gimp_drawable_bpp ( drawable - > id ) ;
pf - > img_has_alpha = gimp_drawable_has_alpha ( drawable - > id ) ;
pf - > tile_width = gimp_tile_width ( ) ;
pf - > tile_height = gimp_tile_height ( ) ;
pf - > bg_color [ 0 ] = 0 ;
pf - > bg_color [ 1 ] = 0 ;
pf - > bg_color [ 2 ] = 0 ;
pf - > bg_color [ 3 ] = 0 ;
pf - > drawable = drawable ;
pf - > tile = NULL ;
return pf ;
} /* pixel_fetcher_new */
/*****/
static void
pixel_fetcher_set_bg_color ( pixel_fetcher_t * pf , guchar r , guchar g , guchar b , guchar a )
{
pf - > bg_color [ 0 ] = r ;
pf - > bg_color [ 1 ] = g ;
pf - > bg_color [ 2 ] = b ;
if ( pf - > img_has_alpha )
pf - > bg_color [ pf - > img_bpp - 1 ] = a ;
} /* pixel_fetcher_set_bg_color */
/*****/
static void
pixel_fetcher_get_pixel ( pixel_fetcher_t * pf , int x , int y , guchar * pixel )
{
gint col , row ;
gint coloff , rowoff ;
guchar * p ;
int i ;
if ( ( x < sel_x1 ) | | ( x > = sel_x2 ) | |
( y < sel_y1 ) | | ( y > = sel_y2 ) ) {
for ( i = 0 ; i < pf - > img_bpp ; i + + )
pixel [ i ] = pf - > bg_color [ i ] ;
return ;
} /* if */
col = x / pf - > tile_width ;
coloff = x % pf - > tile_width ;
row = y / pf - > tile_height ;
rowoff = y % pf - > tile_height ;
if ( ( col ! = pf - > col ) | |
( row ! = pf - > row ) | |
( pf - > tile = = NULL ) ) {
if ( pf - > tile ! = NULL )
gimp_tile_unref ( pf - > tile , FALSE ) ;
pf - > tile = gimp_drawable_get_tile ( pf - > drawable , FALSE , row , col ) ;
gimp_tile_ref ( pf - > tile ) ;
pf - > col = col ;
pf - > row = row ;
} /* if */
p = pf - > tile - > data + pf - > img_bpp * ( pf - > tile - > ewidth * rowoff + coloff ) ;
for ( i = pf - > img_bpp ; i ; i - - )
* pixel + + = * p + + ;
} /* pixel_fetcher_get_pixel */
/*****/
static void
pixel_fetcher_destroy ( pixel_fetcher_t * pf )
{
if ( pf - > tile ! = NULL )
gimp_tile_unref ( pf - > tile , FALSE ) ;
g_free ( pf ) ;
} /* pixel_fetcher_destroy */
/*****/
static void
build_preview_source_image ( void )
{
double left , right , bottom , top ;
double px , py ;
double dx , dy ;
int x , y ;
guchar * p ;
guchar pixel [ 4 ] ;
pixel_fetcher_t * pf ;
1998-02-20 18:41:20 +08:00
pcint . check_row_0 = g_malloc ( preview_width * sizeof ( guchar ) ) ;
pcint . check_row_1 = g_malloc ( preview_width * sizeof ( guchar ) ) ;
pcint . image = g_malloc ( preview_width * preview_height * 4 * sizeof ( guchar ) ) ;
pcint . dimage = g_malloc ( preview_width * preview_height * 3 * sizeof ( guchar ) ) ;
1997-11-25 06:05:25 +08:00
left = sel_x1 ;
right = sel_x2 - 1 ;
bottom = sel_y2 - 1 ;
top = sel_y1 ;
dx = ( right - left ) / ( preview_width - 1 ) ;
dy = ( bottom - top ) / ( preview_height - 1 ) ;
py = top ;
pf = pixel_fetcher_new ( drawable ) ;
1998-02-20 18:41:20 +08:00
p = pcint . image ;
1997-11-25 06:05:25 +08:00
for ( y = 0 ; y < preview_height ; y + + ) {
px = left ;
for ( x = 0 ; x < preview_width ; x + + ) {
/* Checks */
if ( ( x / CHECK_SIZE ) & 1 ) {
1998-02-20 18:41:20 +08:00
pcint . check_row_0 [ x ] = CHECK_DARK ;
pcint . check_row_1 [ x ] = CHECK_LIGHT ;
1997-11-25 06:05:25 +08:00
} else {
1998-02-20 18:41:20 +08:00
pcint . check_row_0 [ x ] = CHECK_LIGHT ;
pcint . check_row_1 [ x ] = CHECK_DARK ;
1997-11-25 06:05:25 +08:00
} /* else */
/* Thumbnail image */
pixel_fetcher_get_pixel ( pf , ( int ) px , ( int ) py , pixel ) ;
if ( img_bpp < 3 ) {
if ( img_has_alpha )
pixel [ 3 ] = pixel [ 1 ] ;
else
pixel [ 3 ] = 255 ;
pixel [ 1 ] = pixel [ 0 ] ;
pixel [ 2 ] = pixel [ 0 ] ;
} else
if ( ! img_has_alpha )
pixel [ 3 ] = 255 ;
* p + + = pixel [ 0 ] ;
* p + + = pixel [ 1 ] ;
* p + + = pixel [ 2 ] ;
* p + + = pixel [ 3 ] ;
px + = dx ;
} /* for */
py + = dy ;
} /* for */
pixel_fetcher_destroy ( pf ) ;
} /* build_preview_source_image */
/*****/
static gint
polarize_dialog ( void )
{
GtkWidget * dialog ;
GtkWidget * top_table ;
GtkWidget * frame ;
GtkWidget * table ;
GtkWidget * button ;
GtkWidget * toggle ;
GtkWidget * hbox ;
1998-02-20 18:41:20 +08:00
GtkTooltips * tips ;
GdkColor tips_fg , tips_bg ;
1997-11-25 06:05:25 +08:00
gint argc ;
gchar * * argv ;
guchar * color_cube ;
#if 0
printf ( " Waiting... (pid %d) \n " , getpid ( ) ) ;
kill ( getpid ( ) , SIGSTOP ) ;
# endif
argc = 1 ;
argv = g_new ( gchar * , 1 ) ;
argv [ 0 ] = g_strdup ( " polarize " ) ;
gtk_init ( & argc , & argv ) ;
gtk_rc_parse ( gimp_gtkrc ( ) ) ;
gdk_set_use_xshm ( gimp_use_xshm ( ) ) ;
gtk_preview_set_gamma ( gimp_gamma ( ) ) ;
gtk_preview_set_install_cmap ( gimp_install_cmap ( ) ) ;
color_cube = gimp_color_cube ( ) ;
gtk_preview_set_color_cube ( color_cube [ 0 ] , color_cube [ 1 ] , color_cube [ 2 ] , color_cube [ 3 ] ) ;
gtk_widget_set_default_visual ( gtk_preview_get_visual ( ) ) ;
gtk_widget_set_default_colormap ( gtk_preview_get_cmap ( ) ) ;
build_preview_source_image ( ) ;
dialog = gtk_dialog_new ( ) ;
gtk_window_set_title ( GTK_WINDOW ( dialog ) , " Polarize " ) ;
gtk_window_position ( GTK_WINDOW ( dialog ) , GTK_WIN_POS_MOUSE ) ;
gtk_container_border_width ( GTK_CONTAINER ( dialog ) , 0 ) ;
gtk_signal_connect ( GTK_OBJECT ( dialog ) , " destroy " ,
( GtkSignalFunc ) dialog_close_callback ,
NULL ) ;
top_table = gtk_table_new ( 3 , 3 , FALSE ) ;
gtk_container_border_width ( GTK_CONTAINER ( top_table ) , 6 ) ;
gtk_table_set_row_spacings ( GTK_TABLE ( top_table ) , 4 ) ;
gtk_box_pack_start ( GTK_BOX ( GTK_DIALOG ( dialog ) - > vbox ) , top_table , FALSE , FALSE , 0 ) ;
gtk_widget_show ( top_table ) ;
1998-02-20 18:41:20 +08:00
/* Initialize Tooltips */
/* use black as foreground: */
tips = gtk_tooltips_new ( ) ;
tips_fg . red = 0 ;
tips_fg . green = 0 ;
tips_fg . blue = 0 ;
/* postit yellow (khaki) as background: */
gdk_color_alloc ( gtk_widget_get_colormap ( dialog ) , & tips_fg ) ;
tips_bg . red = 61669 ;
tips_bg . green = 59113 ;
tips_bg . blue = 35979 ;
gdk_color_alloc ( gtk_widget_get_colormap ( dialog ) , & tips_bg ) ;
gtk_tooltips_set_colors ( tips , & tips_bg , & tips_fg ) ;
1997-11-25 06:05:25 +08:00
/* Preview */
frame = gtk_frame_new ( NULL ) ;
gtk_frame_set_shadow_type ( GTK_FRAME ( frame ) , GTK_SHADOW_IN ) ;
gtk_table_attach ( GTK_TABLE ( top_table ) , frame , 1 , 2 , 0 , 1 , 0 , 0 , 0 , 0 ) ;
gtk_widget_show ( frame ) ;
1998-02-20 18:41:20 +08:00
pcint . preview = gtk_preview_new ( GTK_PREVIEW_COLOR ) ;
gtk_preview_size ( GTK_PREVIEW ( pcint . preview ) , preview_width , preview_height ) ;
gtk_container_add ( GTK_CONTAINER ( frame ) , pcint . preview ) ;
gtk_widget_show ( pcint . preview ) ;
1997-11-25 06:05:25 +08:00
/* Controls */
table = gtk_table_new ( 2 , 3 , FALSE ) ;
gtk_container_border_width ( GTK_CONTAINER ( table ) , 0 ) ;
gtk_table_attach ( GTK_TABLE ( top_table ) , table , 0 , 3 , 1 , 2 , GTK_EXPAND | GTK_FILL , 0 , 0 , 0 ) ;
gtk_widget_show ( table ) ;
1998-02-20 18:41:20 +08:00
dialog_create_value ( " Circle depth in percent " , GTK_TABLE ( table ) , 0 , & pcvals . circle , 0 , 100.0 , 1.0 ) ;
dialog_create_value ( " Offset angle " , GTK_TABLE ( table ) , 1 , & pcvals . angle , 0 , 359 , 1.0 ) ;
1997-11-25 06:05:25 +08:00
/* togglebuttons for backwards, top, polar->rectangular */
hbox = gtk_hbox_new ( TRUE , 2 ) ;
gtk_table_attach ( GTK_TABLE ( top_table ) , hbox , 0 , 3 , 2 , 3 ,
GTK_FILL , 0 , 0 , 0 ) ;
1998-02-20 18:41:20 +08:00
toggle = gtk_check_button_new_with_label ( " Map Backwards " ) ;
gtk_toggle_button_set_state ( GTK_TOGGLE_BUTTON ( toggle ) , pcvals . backwards ) ;
1998-03-19 19:30:56 +08:00
gtk_signal_connect ( GTK_OBJECT ( toggle ) , " toggled " ,
1998-02-20 18:41:20 +08:00
( GtkSignalFunc ) polar_toggle_callback ,
& pcvals . backwards ) ;
1997-11-25 06:05:25 +08:00
gtk_box_pack_start ( GTK_BOX ( hbox ) , toggle , TRUE , TRUE , 0 ) ;
gtk_widget_show ( toggle ) ;
1998-02-20 18:41:20 +08:00
set_tooltip ( tips , toggle , " If checked the mapping will begin at the right side, as opposed to beginning at the left. " ) ;
1997-11-25 06:05:25 +08:00
1998-02-20 18:41:20 +08:00
toggle = gtk_check_button_new_with_label ( " Map from Top " ) ;
gtk_toggle_button_set_state ( GTK_TOGGLE_BUTTON ( toggle ) , pcvals . inverse ) ;
1998-03-19 19:30:56 +08:00
gtk_signal_connect ( GTK_OBJECT ( toggle ) , " toggled " ,
1998-02-20 18:41:20 +08:00
( GtkSignalFunc ) polar_toggle_callback ,
& pcvals . inverse ) ;
1997-11-25 06:05:25 +08:00
gtk_box_pack_start ( GTK_BOX ( hbox ) , toggle , TRUE , TRUE , 0 ) ;
gtk_widget_show ( toggle ) ;
1998-02-20 18:41:20 +08:00
set_tooltip ( tips , toggle , " If unchecked the mapping will put the bottom row in the middle and the top row on the outside. If checked it will be the opposite. " ) ;
1997-11-25 06:05:25 +08:00
1998-02-20 18:41:20 +08:00
toggle = gtk_check_button_new_with_label ( " Polar to Rectangular " ) ;
gtk_toggle_button_set_state ( GTK_TOGGLE_BUTTON ( toggle ) , pcvals . polrec ) ;
1998-03-19 19:30:56 +08:00
gtk_signal_connect ( GTK_OBJECT ( toggle ) , " toggled " ,
1998-02-20 18:41:20 +08:00
( GtkSignalFunc ) polar_toggle_callback ,
& pcvals . polrec ) ;
1997-11-25 06:05:25 +08:00
gtk_box_pack_start ( GTK_BOX ( hbox ) , toggle , TRUE , TRUE , 0 ) ;
gtk_widget_show ( toggle ) ;
1998-02-20 18:41:20 +08:00
set_tooltip ( tips , toggle , " If unchecked the image will be circularly mapped onto a rectangle. If checked the image will be mapped onto a circle. " ) ;
1997-11-25 06:05:25 +08:00
gtk_widget_show ( hbox ) ;
/* Buttons */
gtk_container_border_width ( GTK_CONTAINER ( GTK_DIALOG ( dialog ) - > action_area ) , 6 ) ;
button = gtk_button_new_with_label ( " OK " ) ;
GTK_WIDGET_SET_FLAGS ( button , GTK_CAN_DEFAULT ) ;
gtk_signal_connect ( GTK_OBJECT ( button ) , " clicked " ,
( GtkSignalFunc ) dialog_ok_callback ,
dialog ) ;
gtk_box_pack_start ( GTK_BOX ( GTK_DIALOG ( dialog ) - > action_area ) , button , TRUE , TRUE , 0 ) ;
gtk_widget_grab_default ( button ) ;
gtk_widget_show ( button ) ;
button = gtk_button_new_with_label ( " Cancel " ) ;
GTK_WIDGET_SET_FLAGS ( button , GTK_CAN_DEFAULT ) ;
gtk_signal_connect ( GTK_OBJECT ( button ) , " clicked " ,
( GtkSignalFunc ) dialog_cancel_callback ,
dialog ) ;
gtk_box_pack_start ( GTK_BOX ( GTK_DIALOG ( dialog ) - > action_area ) , button , TRUE , TRUE , 0 ) ;
gtk_widget_show ( button ) ;
/* Done */
gtk_widget_show ( dialog ) ;
dialog_update_preview ( ) ;
gtk_main ( ) ;
1998-02-20 18:41:20 +08:00
gtk_object_unref ( GTK_OBJECT ( tips ) ) ;
1997-11-25 06:05:25 +08:00
gdk_flush ( ) ;
1998-02-20 18:41:20 +08:00
g_free ( pcint . check_row_0 ) ;
g_free ( pcint . check_row_1 ) ;
g_free ( pcint . image ) ;
g_free ( pcint . dimage ) ;
1997-11-25 06:05:25 +08:00
1998-02-20 18:41:20 +08:00
return pcint . run ;
1997-11-25 06:05:25 +08:00
} /* polarize_dialog */
/*****/
static void
dialog_update_preview ( void )
{
double left , right , bottom , top ;
double dx , dy ;
double px , py ;
double cx , cy ;
int ix , iy ;
int x , y ;
double scale_x , scale_y ;
guchar * p_ul , * i , * p ;
guchar * check_ul ;
int check ;
guchar outside [ 4 ] ;
gimp_palette_get_background ( & outside [ 0 ] , & outside [ 1 ] , & outside [ 2 ] ) ;
outside [ 3 ] = ( img_has_alpha ? 0 : 255 ) ;
if ( img_bpp < 3 ) {
outside [ 1 ] = outside [ 0 ] ;
outside [ 2 ] = outside [ 0 ] ;
} /* if */
left = sel_x1 ;
right = sel_x2 - 1 ;
bottom = sel_y2 - 1 ;
top = sel_y1 ;
dx = ( right - left ) / ( preview_width - 1 ) ;
dy = ( bottom - top ) / ( preview_height - 1 ) ;
scale_x = ( double ) preview_width / ( right - left + 1 ) ;
scale_y = ( double ) preview_height / ( bottom - top + 1 ) ;
py = top ;
1998-02-20 18:41:20 +08:00
p_ul = pcint . dimage ;
/* p_lr = pcint.dimage + 3 * (preview_width * preview_height - 1);*/
1997-11-25 06:05:25 +08:00
for ( y = 0 ; y < preview_height ; y + + ) {
px = left ;
if ( ( y / CHECK_SIZE ) & 1 )
1998-02-20 18:41:20 +08:00
check_ul = pcint . check_row_0 ;
1997-11-25 06:05:25 +08:00
else
1998-02-20 18:41:20 +08:00
check_ul = pcint . check_row_1 ;
1997-11-25 06:05:25 +08:00
for ( x = 0 ; x < preview_width ; x + + ) {
calc_undistorted_coords ( px , py , & cx , & cy ) ;
cx = ( cx - left ) * scale_x ;
cy = ( cy - top ) * scale_y ;
ix = ( int ) ( cx + 0.5 ) ;
iy = ( int ) ( cy + 0.5 ) ;
check = check_ul [ x ] ;
if ( ( ix > = 0 ) & & ( ix < preview_width ) & &
( iy > = 0 ) & & ( iy < preview_height ) )
1998-02-20 18:41:20 +08:00
i = pcint . image + 4 * ( preview_width * iy + ix ) ;
1997-11-25 06:05:25 +08:00
else
i = outside ;
p_ul [ 0 ] = check + ( ( i [ 0 ] - check ) * i [ 3 ] ) / 255 ;
p_ul [ 1 ] = check + ( ( i [ 1 ] - check ) * i [ 3 ] ) / 255 ;
p_ul [ 2 ] = check + ( ( i [ 2 ] - check ) * i [ 3 ] ) / 255 ;
p_ul + = 3 ;
px + = dx ;
} /* for */
py + = dy ;
} /* for */
1998-02-20 18:41:20 +08:00
p = pcint . dimage ;
1997-11-25 06:05:25 +08:00
for ( y = 0 ; y < img_height ; y + + ) {
1998-02-20 18:41:20 +08:00
gtk_preview_draw_row ( GTK_PREVIEW ( pcint . preview ) , p , 0 , y , preview_width ) ;
1997-11-25 06:05:25 +08:00
p + = preview_width * 3 ;
} /* for */
1998-02-20 18:41:20 +08:00
gtk_widget_draw ( pcint . preview , NULL ) ;
1997-11-25 06:05:25 +08:00
gdk_flush ( ) ;
} /* dialog_update_preview */
/*****/
static void
dialog_create_value ( char * title , GtkTable * table , int row , gdouble * value ,
double left , double right , double step )
{
GtkWidget * label ;
GtkWidget * scale ;
GtkWidget * entry ;
GtkObject * scale_data ;
char buf [ 256 ] ;
label = gtk_label_new ( title ) ;
gtk_misc_set_alignment ( GTK_MISC ( label ) , 0.0 , 0.5 ) ;
gtk_table_attach ( table , label , 0 , 1 , row , row + 1 , GTK_FILL , GTK_FILL , 4 , 0 ) ;
gtk_widget_show ( label ) ;
scale_data = gtk_adjustment_new ( * value , left , right ,
step ,
step ,
0.0 ) ;
gtk_signal_connect ( GTK_OBJECT ( scale_data ) , " value_changed " ,
( GtkSignalFunc ) dialog_scale_update ,
value ) ;
scale = gtk_hscale_new ( GTK_ADJUSTMENT ( scale_data ) ) ;
gtk_widget_set_usize ( scale , SCALE_WIDTH , 0 ) ;
gtk_table_attach ( table , scale , 1 , 2 , row , row + 1 , GTK_EXPAND | GTK_FILL , GTK_FILL , 0 , 0 ) ;
gtk_scale_set_draw_value ( GTK_SCALE ( scale ) , FALSE ) ;
gtk_scale_set_digits ( GTK_SCALE ( scale ) , 3 ) ;
gtk_range_set_update_policy ( GTK_RANGE ( scale ) , GTK_UPDATE_CONTINUOUS ) ;
gtk_widget_show ( scale ) ;
entry = gtk_entry_new ( ) ;
gtk_object_set_user_data ( GTK_OBJECT ( entry ) , scale_data ) ;
gtk_object_set_user_data ( scale_data , entry ) ;
gtk_widget_set_usize ( entry , ENTRY_WIDTH , 0 ) ;
sprintf ( buf , " %0.3f " , * value ) ;
gtk_entry_set_text ( GTK_ENTRY ( entry ) , buf ) ;
gtk_signal_connect ( GTK_OBJECT ( entry ) , " changed " ,
( GtkSignalFunc ) dialog_entry_update ,
value ) ;
gtk_table_attach ( GTK_TABLE ( table ) , entry , 2 , 3 , row , row + 1 , GTK_FILL , GTK_FILL , 4 , 0 ) ;
gtk_widget_show ( entry ) ;
} /* dialog_create_value */
/*****/
static void
dialog_scale_update ( GtkAdjustment * adjustment , gdouble * value )
{
GtkWidget * entry ;
char buf [ 256 ] ;
if ( * value ! = adjustment - > value ) {
* value = adjustment - > value ;
entry = gtk_object_get_user_data ( GTK_OBJECT ( adjustment ) ) ;
sprintf ( buf , " %0.3f " , * value ) ;
gtk_signal_handler_block_by_data ( GTK_OBJECT ( entry ) , value ) ;
gtk_entry_set_text ( GTK_ENTRY ( entry ) , buf ) ;
gtk_signal_handler_unblock_by_data ( GTK_OBJECT ( entry ) , value ) ;
dialog_update_preview ( ) ;
} /* if */
} /* dialog_scale_update */
/*****/
static void
dialog_entry_update ( GtkWidget * widget , gdouble * value )
{
GtkAdjustment * adjustment ;
gdouble new_value ;
new_value = atof ( gtk_entry_get_text ( GTK_ENTRY ( widget ) ) ) ;
if ( * value ! = new_value ) {
adjustment = gtk_object_get_user_data ( GTK_OBJECT ( widget ) ) ;
if ( ( new_value > = adjustment - > lower ) & &
( new_value < = adjustment - > upper ) ) {
* value = new_value ;
adjustment - > value = new_value ;
gtk_signal_emit_by_name ( GTK_OBJECT ( adjustment ) , " value_changed " ) ;
dialog_update_preview ( ) ;
} /* if */
} /* if */
} /* dialog_entry_update */
/*****/
static void
dialog_close_callback ( GtkWidget * widget , gpointer data )
{
gtk_main_quit ( ) ;
} /* dialog_close_callback */
/*****/
static void
dialog_ok_callback ( GtkWidget * widget , gpointer data )
{
1998-02-20 18:41:20 +08:00
pcint . run = TRUE ;
1997-11-25 06:05:25 +08:00
gtk_widget_destroy ( GTK_WIDGET ( data ) ) ;
} /* dialog_ok_callback */
/*****/
static void
dialog_cancel_callback ( GtkWidget * widget , gpointer data )
{
gtk_widget_destroy ( GTK_WIDGET ( data ) ) ;
} /* dialog_cancel_callback */
1998-02-20 18:41:20 +08:00
static void polar_toggle_callback ( GtkWidget * widget , gpointer data )
1997-11-25 06:05:25 +08:00
{
1998-03-19 19:30:56 +08:00
gint * toggle_val ;
1998-02-20 18:41:20 +08:00
1998-03-19 19:30:56 +08:00
toggle_val = ( gint * ) data ;
1998-02-20 18:41:20 +08:00
if ( GTK_TOGGLE_BUTTON ( widget ) - > active )
* toggle_val = TRUE ;
else
* toggle_val = FALSE ;
1997-11-25 06:05:25 +08:00
dialog_update_preview ( ) ;
}
static void
1998-02-20 18:41:20 +08:00
set_tooltip ( GtkTooltips * tooltips , GtkWidget * widget , const char * desc )
1997-11-25 06:05:25 +08:00
{
1998-02-20 18:41:20 +08:00
if ( desc & & desc [ 0 ] )
1998-02-21 11:42:10 +08:00
gtk_tooltips_set_tip ( tooltips , widget , ( char * ) desc , NULL ) ;
1997-11-25 06:05:25 +08:00
}