Пример #1
0
Handle<Value> MerlinImage::BlurImage(const Arguments& args) {
    HandleScope scope;
    MagickWand* wand = MerlinImage::ReadImage(  ObjectWrap::Unwrap<MerlinImage>(args.This()) );

    const double radius = args[0]->NumberValue();
    const double sigma = args[1]->NumberValue();
    MagickBlurImage(wand, radius, sigma);

    return scope.Close(MerlinImage::WriteImage(wand));
}
Пример #2
0
MagickBooleanType MagickBlurImageChannel(MagickWand *wand,
  const ChannelType channel,const double radius,const double sigma) {
	MagickBooleanType status;
	ChannelType previous_channel_mask;

	if (channel != UndefinedChannel) {
		previous_channel_mask = MagickSetImageChannelMask(wand, channel);
	}

	status = MagickBlurImage(wand, radius,sigma);

	if (channel != UndefinedChannel) {
		(void) MagickSetImageChannelMask(wand, previous_channel_mask);
	}

	return status;
}
// output_data
apr_status_t small_light_filter_imagemagick_output_data(
    ap_filter_t *f,
    apr_bucket_brigade *bb,
    void *v_ctx,
    apr_bucket *e)
{
    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "small_light_filter_imagemagick_output_data");

    request_rec *r = f->r;
    small_light_module_ctx_t* ctx = (small_light_module_ctx_t*)v_ctx;
    small_light_module_imagemagick_ctx_t *lctx = ctx->lctx;
    struct timeval t2, t21, t22, t23, t3;
    MagickBooleanType status = MagickFalse;

    // check data received.
    if (lctx->image == NULL) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "no data received.");
        r->status = HTTP_INTERNAL_SERVER_ERROR;
        return APR_EGENERAL;
    }

    // start image modifing.
    gettimeofday(&t2, NULL);
    small_light_image_size_t sz;
    small_light_calc_image_size(&sz, r, ctx, 10000.0, 10000.0);

    // init wand
    small_light_filter_imagemagick_output_data_init();
    lctx->wand = NewMagickWand();

    // prepare.
    if (sz.jpeghint_flg != 0) {
        char *jpeg_size_opt = (char *)apr_psprintf(r->pool, "%dx%d",
            (int)sz.dw, (int)sz.dh);
        MagickSetOption(lctx->wand, "jpeg:size", jpeg_size_opt);
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "MagickSetOption(jpeg:size, %s)", jpeg_size_opt);
    }

    // load image.
    gettimeofday(&t21, NULL);
    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "MagickReadImageBlob");
    status = MagickReadImageBlob(lctx->wand, (void *)lctx->image, lctx->image_len);
    if (status == MagickFalse) {
        small_light_filter_imagemagick_output_data_fini(ctx);
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "couldn't read image");
        r->status = HTTP_INTERNAL_SERVER_ERROR;
        return APR_EGENERAL;
    }

    // calc size.
    gettimeofday(&t22, NULL);
    double iw = (double)MagickGetImageWidth(lctx->wand);
    double ih = (double)MagickGetImageHeight(lctx->wand);
    small_light_calc_image_size(&sz, r, ctx, iw, ih);

    // pass through.
    if (sz.pt_flg != 0) {
        small_light_filter_imagemagick_output_data_fini(ctx);
        apr_bucket *b = apr_bucket_pool_create(lctx->image, lctx->image_len, r->pool, ctx->bb->bucket_alloc);
        APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
        APR_BRIGADE_INSERT_TAIL(ctx->bb, apr_bucket_eos_create(ctx->bb->bucket_alloc));
        return ap_pass_brigade(f->next, ctx->bb);
    }

    // crop, scale.
    status = MagickTrue;
    if (sz.scale_flg != 0) {
        char *crop_geo = (char *)apr_psprintf(r->pool, "%f!x%f!+%f+%f",
            sz.sw, sz.sh, sz.sx, sz.sy);
        char *size_geo = (char *)apr_psprintf(r->pool, "%f!x%f!", sz.dw, sz.dh);
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
            "MagickTransformImage(wand, ""%s"", ""%s"")",
            crop_geo, size_geo);
        MagickWand *trans_wand;
        trans_wand = MagickTransformImage(lctx->wand, crop_geo, size_geo);
        if (trans_wand == NULL || trans_wand == lctx->wand) {
            small_light_filter_imagemagick_output_data_fini(ctx);
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "MagickTransformImage failed");
            r->status = HTTP_INTERNAL_SERVER_ERROR;
            return APR_EGENERAL;
        }
        DestroyMagickWand(lctx->wand);
        lctx->wand = trans_wand;
    } else {
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "no scale");
    }

    // create canvas then draw image to the canvas.
    if (sz.cw > 0.0 && sz.ch > 0.0) {
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "NewMagickWand()");
        MagickWand *canvas_wand = NewMagickWand();
        PixelWand *canvas_color = NewPixelWand();
        PixelSetRed(canvas_color, sz.cc.r / 255.0);
        PixelSetGreen(canvas_color, sz.cc.g / 255.0);
        PixelSetBlue(canvas_color, sz.cc.b / 255.0);
        PixelSetAlpha(canvas_color, sz.cc.a / 255.0);
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
            "MagickNewImage(canvas_wand, %f, %f, bgcolor)", sz.cw, sz.ch);
        status = MagickNewImage(canvas_wand, sz.cw, sz.ch, canvas_color);
        DestroyPixelWand(canvas_color);
        if (status == MagickFalse) {
            small_light_filter_imagemagick_output_data_fini(ctx);
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                "MagickNewImage(canvas_wand, %f, %f, bgcolor) failed", sz.cw, sz.ch);
            r->status = HTTP_INTERNAL_SERVER_ERROR;
            return APR_EGENERAL;
        }
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
            "MagickCompositeImage(canvas_wand, wand, AtopCompositeOp, %f, %f)",
            sz.dx, sz.dy);
        status = MagickCompositeImage(canvas_wand, lctx->wand, AtopCompositeOp, sz.dx, sz.dy);
        if (status == MagickFalse) {
            small_light_filter_imagemagick_output_data_fini(ctx);
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                "MagickCompositeImage(canvas_wand, wand, AtopCompositeOp, %f, %f) failed",
                sz.dx, sz.dy);
            r->status = HTTP_INTERNAL_SERVER_ERROR;
            return APR_EGENERAL;
        }
        DestroyMagickWand(lctx->wand);
        lctx->wand = canvas_wand;
    }

    // effects.
    char *unsharp = (char *)apr_table_get(ctx->prm, "unsharp");
    if (unsharp) {
        GeometryInfo geo;
        ParseGeometry(unsharp, &geo);
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
            "MagickUnsharpMaskImage(wand, %f, %f, %f, %f)",
            geo.rho, geo.sigma, geo.xi, geo.psi);
        status = MagickUnsharpMaskImage(lctx->wand, geo.rho, geo.sigma, geo.xi, geo.psi);
        if (status == MagickFalse) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unsharp failed");
        }
    }

    char *sharpen = (char *)apr_table_get(ctx->prm, "sharpen");
    if (sharpen) {
        GeometryInfo geo;
        ParseGeometry(sharpen, &geo);
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
            "MagickSharpenImage(wand, %f, %f)",
            geo.rho, geo.sigma);
        status = MagickSharpenImage(lctx->wand, geo.rho, geo.sigma);
        if (status == MagickFalse) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "sharpen failed");
        }
    }

    char *blur = (char *)apr_table_get(ctx->prm, "blur");
    if (blur) {
        GeometryInfo geo;
        ParseGeometry(blur, &geo);
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
            "MagickBlurImage(wand, %f, %f)",
            geo.rho, geo.sigma);
        status = MagickBlurImage(lctx->wand, geo.rho, geo.sigma);
        if (status == MagickFalse) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "blur failed");
        }
    }

    // border.
    if (sz.bw > 0.0 || sz.bh > 0.0) {
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "draw border");
        DrawingWand *border_wand = NewDrawingWand();
        PixelWand *border_color;
        border_color = NewPixelWand();
        PixelSetRed(border_color, sz.bc.r / 255.0);
        PixelSetGreen(border_color, sz.bc.g / 255.0);
        PixelSetBlue(border_color, sz.bc.b / 255.0);
        PixelSetAlpha(border_color, sz.bc.a / 255.0);
        DrawSetFillColor(border_wand, border_color);
        DrawSetStrokeColor(border_wand, border_color);
        DrawSetStrokeWidth(border_wand, 1);
        DrawRectangle(border_wand, 0, 0, sz.cw - 1, sz.bh - 1);
        DrawRectangle(border_wand, 0, 0, sz.bw - 1, sz.ch - 1);
        DrawRectangle(border_wand, 0, sz.ch - sz.bh, sz.cw - 1, sz.ch - 1);
        DrawRectangle(border_wand, sz.cw - sz.bw, 0, sz.cw - 1, sz.ch - 1);
        MagickDrawImage(lctx->wand, border_wand);
        DestroyPixelWand(border_color);
        DestroyDrawingWand(border_wand);
    }

    gettimeofday(&t23, NULL);

    // set params.
    double q = small_light_parse_double(r, (char *)apr_table_get(ctx->prm, "q"));
    if (q > 0.0) {
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
            "MagickSetImageComressionQualty(wand, %f)", q);
        MagickSetImageCompressionQuality(lctx->wand, q);
    }
    char *of = (char *)apr_table_get(ctx->prm, "of");
    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
        "MagickSetFormat(wand, '%s')", of);
    MagickSetFormat(lctx->wand, of);

    // get small_lighted image as binary.
    unsigned char *canvas_buff;
    const char *sled_image;
    size_t sled_image_size;
    canvas_buff = MagickGetImageBlob(lctx->wand, &sled_image_size);
    sled_image = (const char *)apr_pmemdup(r->pool, canvas_buff, sled_image_size);
    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "sled_image_size = %d", sled_image_size);

    // free buffer and wand.
    MagickRelinquishMemory(canvas_buff);
    small_light_filter_imagemagick_output_data_fini(ctx);

    // insert new bucket to bucket brigade.
    apr_bucket *b = apr_bucket_pool_create(sled_image, sled_image_size, r->pool, ctx->bb->bucket_alloc);
    APR_BRIGADE_INSERT_TAIL(ctx->bb, b);

    // insert eos to bucket brigade.
    APR_BRIGADE_INSERT_TAIL(ctx->bb, apr_bucket_eos_create(ctx->bb->bucket_alloc));

    // set correct Content-Type and Content-Length.
    char *cont_type = apr_psprintf(r->pool, "image/%s", of);
    ap_set_content_type(r, cont_type);
    ap_set_content_length(r, sled_image_size);

    // end.
    gettimeofday(&t3, NULL);

    // http header.
    int info = small_light_parse_int(r, (char *)apr_table_get(ctx->prm, "info"));
    if (info != SMALL_LIGHT_INT_INVALID_VALUE && info != 0) {
        char *info = (char *)apr_psprintf(r->pool,
            "transfer=%ldms, modify image=%ldms (load=%ldms, scale=%ldms, save=%ldms)",
            small_light_timeval_diff(&ctx->t, &t2) / 1000L,
            small_light_timeval_diff(&t2, &t3) / 1000L,
            small_light_timeval_diff(&t21, &t22) / 1000L,
            small_light_timeval_diff(&t22, &t23) / 1000L,
            small_light_timeval_diff(&t23, &t3) / 1000L
        );
        ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
            "uri=%s, info=%s)", r->unparsed_uri, info);
        apr_table_setn(r->headers_out, "X-SmallLight-Description", info);
    }

    return ap_pass_brigade(f->next, ctx->bb);
}
ngx_int_t ngx_http_small_light_imagemagick_process(ngx_http_request_t *r, ngx_http_small_light_ctx_t *ctx)
{
    ngx_http_small_light_imagemagick_ctx_t *ictx;
    ngx_http_small_light_image_size_t       sz;
    MagickBooleanType                       status;
    int                                     rmprof_flg, progressive_flg, cmyk2rgb_flg;
    double                                  iw, ih, q;
    char                                   *unsharp, *sharpen, *blur, *of, *of_orig;
    MagickWand                             *trans_wand, *canvas_wand;
    DrawingWand                            *border_wand;
    PixelWand                              *bg_color, *canvas_color, *border_color;
    GeometryInfo                            geo;
    ngx_fd_t                                fd;
    MagickWand                             *icon_wand;
    u_char                                 *p, *embedicon;
    size_t                                  embedicon_path_len, embedicon_len, sled_image_size;
    ngx_int_t                               type;
    u_char                                  jpeg_size_opt[32], crop_geo[128], size_geo[128], embedicon_path[256];
    ColorspaceType                          color_space;
#if MagickLibVersion >= 0x690
    int                                     autoorient_flg;
#endif

    status = MagickFalse;

    ictx = (ngx_http_small_light_imagemagick_ctx_t *)ctx->ictx;

    /* adjust image size */
    ngx_http_small_light_calc_image_size(r, ctx, &sz, 10000.0, 10000.0);

    /* prepare */
    if (sz.jpeghint_flg != 0) {
        p = ngx_snprintf((u_char *)jpeg_size_opt, sizeof(jpeg_size_opt) - 1, "%dx%d", (ngx_int_t)sz.dw, (ngx_int_t)sz.dh);
        *p = '\0';
        ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "jpeg_size_opt:%s", jpeg_size_opt);
        MagickSetOption(ictx->wand, "jpeg:size", (char *)jpeg_size_opt);
    }

    /* load image. */
    status = MagickReadImageBlob(ictx->wand, (void *)ictx->image, ictx->image_len);
    if (status == MagickFalse) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "couldn't read image %s:%d",
                      __FUNCTION__,
                      __LINE__);
        return NGX_ERROR;
    }

    MagickSetFirstIterator(ictx->wand);

    color_space = MagickGetImageColorspace(ictx->wand);

    /* remove all profiles */
    rmprof_flg = ngx_http_small_light_parse_flag(NGX_HTTP_SMALL_LIGHT_PARAM_GET_LIT(&ctx->hash, "rmprof"));
    if (rmprof_flg != 0) {
        status = MagickProfileImage(ictx->wand, "*", NULL, 0);
        if (status == MagickFalse) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "couldn't profiling image %s:%d",
                          __FUNCTION__,
                          __LINE__);
        }
    }

    of_orig = MagickGetImageFormat(ictx->wand);
    status = MagickTrue;

#if MagickLibVersion >= 0x690
    /* auto-orient */
    autoorient_flg = ngx_http_small_light_parse_flag(NGX_HTTP_SMALL_LIGHT_PARAM_GET_LIT(&ctx->hash, "autoorient"));
    if (autoorient_flg != 0) {
        status = MagickAutoOrientImage(ictx->wand);
        if (status == MagickFalse) {
            r->err_status = NGX_HTTP_INTERNAL_SERVER_ERROR;
            DestroyString(of_orig);
            return NGX_ERROR;
        }
    }
#endif

    /* rotate. */
    if (sz.angle) {
        bg_color = NewPixelWand();
        PixelSetRed(bg_color,   sz.cc.r / 255.0);
        PixelSetGreen(bg_color, sz.cc.g / 255.0);
        PixelSetBlue(bg_color,  sz.cc.b / 255.0);
        PixelSetAlpha(bg_color, sz.cc.a / 255.0);

        switch (sz.angle) {
        case 90:
        case 180:
        case 270:
            MagickRotateImage(ictx->wand, bg_color, sz.angle);
            break;
        default:
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "image not rotated. 'angle'(%ui) must be 90 or 180 or 270. %s:%d",
                          sz.angle,
                          __FUNCTION__,
                          __LINE__);
            break;
        }

        DestroyPixelWand(bg_color);
    }

    /* calc size. */
    iw = (double)MagickGetImageWidth(ictx->wand);
    ih = (double)MagickGetImageHeight(ictx->wand);
    ngx_http_small_light_calc_image_size(r, ctx, &sz, iw, ih);

    /* pass through. */
    if (sz.pt_flg != 0) {
        ctx->of = ctx->inf;
        DestroyString(of_orig);
        return NGX_OK;
    }

    /* crop, scale. */
    if (sz.scale_flg != 0) {
        p = ngx_snprintf(crop_geo, sizeof(crop_geo) - 1, "%f!x%f!+%f+%f", sz.sw, sz.sh, sz.sx, sz.sy);
        *p = '\0';
        p = ngx_snprintf(size_geo, sizeof(size_geo) - 1, "%f!x%f!",       sz.dw, sz.dh);
        *p = '\0';
        ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "crop_geo:%s", crop_geo);
        ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "size_geo:%s", size_geo);
        MagickResetImagePage(ictx->wand, "+0+0");
        trans_wand = MagickTransformImage(ictx->wand, (char *)crop_geo, (char *)size_geo);
        if (trans_wand == NULL || trans_wand == ictx->wand) {
            r->err_status = NGX_HTTP_INTERNAL_SERVER_ERROR;
            DestroyString(of_orig);
            return NGX_ERROR;
        }
        DestroyMagickWand(ictx->wand);
        ictx->wand = trans_wand;
    }

    /* create canvas then draw image to the canvas. */
    if (sz.cw > 0.0 && sz.ch > 0.0) {
        canvas_wand  = NewMagickWand();
        canvas_color = NewPixelWand();
        PixelSetRed(canvas_color,   sz.cc.r / 255.0);
        PixelSetGreen(canvas_color, sz.cc.g / 255.0);
        PixelSetBlue(canvas_color,  sz.cc.b / 255.0);
        PixelSetAlpha(canvas_color, sz.cc.a / 255.0);
        status = MagickNewImage(canvas_wand, sz.cw, sz.ch, canvas_color);
        DestroyPixelWand(canvas_color);
        if (status == MagickFalse) {
            r->err_status = NGX_HTTP_INTERNAL_SERVER_ERROR;
            DestroyMagickWand(canvas_wand);
            DestroyString(of_orig);
            return NGX_ERROR;
        }

        status = MagickTransformImageColorspace(canvas_wand, color_space);
        if (status == MagickFalse) {
            r->err_status = NGX_HTTP_INTERNAL_SERVER_ERROR;
            DestroyMagickWand(canvas_wand);
            DestroyString(of_orig);
            return NGX_ERROR;
        }

        status = MagickCompositeImage(canvas_wand, ictx->wand, AtopCompositeOp, sz.dx, sz.dy);
        if (status == MagickFalse) {
            r->err_status = NGX_HTTP_INTERNAL_SERVER_ERROR;
            DestroyMagickWand(canvas_wand);
            DestroyString(of_orig);
            return NGX_ERROR;
        }
        DestroyMagickWand(ictx->wand);
        ictx->wand = canvas_wand;
    }

    /* CMYK to sRGB */
    cmyk2rgb_flg = ngx_http_small_light_parse_flag(NGX_HTTP_SMALL_LIGHT_PARAM_GET_LIT(&ctx->hash, "cmyk2rgb"));
    if (cmyk2rgb_flg != 0 && color_space == CMYKColorspace) {
        status = MagickTransformImageColorspace(ictx->wand, sRGBColorspace);
        if (status == MagickFalse) {
            r->err_status = NGX_HTTP_INTERNAL_SERVER_ERROR;
            DestroyString(of_orig);
            return NGX_ERROR;
        }
    }

    /* effects. */
    unsharp = NGX_HTTP_SMALL_LIGHT_PARAM_GET_LIT(&ctx->hash, "unsharp");
    if (ngx_strlen(unsharp) > 0) {
        ParseGeometry(unsharp, &geo);
        if (geo.rho > ctx->radius_max || geo.sigma > ctx->sigma_max) {
            ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
                          "As unsharp geometry is too large, ignored. %s:%d",
                          __FUNCTION__,
                          __LINE__);
        } else {
            status = MagickUnsharpMaskImage(ictx->wand, geo.rho, geo.sigma, geo.xi, geo.psi);
            if (status == MagickFalse) {
                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                              "unsharp failed %s:%d",
                              __FUNCTION__,
                              __LINE__);
            }
        }
    }

    sharpen = NGX_HTTP_SMALL_LIGHT_PARAM_GET_LIT(&ctx->hash, "sharpen");
    if (ngx_strlen(sharpen) > 0) {
        ParseGeometry(sharpen, &geo);
        if (geo.rho > ctx->radius_max || geo.sigma > ctx->sigma_max) {
            ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
                          "As sharpen geometry is too large, ignored. %s:%d",
                          __FUNCTION__,
                          __LINE__);
        } else {
            status = MagickSharpenImage(ictx->wand, geo.rho, geo.sigma);
            if (status == MagickFalse) {
                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                              "sharpen failed %s:%d",
                              __FUNCTION__,
                              __LINE__);
            }
        }
    }

    blur = NGX_HTTP_SMALL_LIGHT_PARAM_GET_LIT(&ctx->hash, "blur");
    if (ngx_strlen(blur) > 0) {
        ParseGeometry(blur, &geo);
        if (geo.rho > ctx->radius_max || geo.sigma > ctx->sigma_max) {
            ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
                          "As blur geometry is too large, ignored. %s:%d",
                          __FUNCTION__,
                          __LINE__);
        } else {
            status = MagickBlurImage(ictx->wand, geo.rho, geo.sigma);
            if (status == MagickFalse) {
                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                              "blur failed %s:%d",
                              __FUNCTION__,
                              __LINE__);
            }
        }
    }

    /* border. */
    if (sz.bw > 0.0 || sz.bh > 0.0) {
        border_wand = NewDrawingWand();
        border_color = NewPixelWand();
        PixelSetRed(border_color,   sz.bc.r / 255.0);
        PixelSetGreen(border_color, sz.bc.g / 255.0);
        PixelSetBlue(border_color,  sz.bc.b / 255.0);
        PixelSetAlpha(border_color, sz.bc.a / 255.0);
        DrawSetFillColor(border_wand, border_color);
        DrawSetStrokeColor(border_wand, border_color);
        DrawSetStrokeWidth(border_wand, 1);

        if (sz.cw > 0.0 && sz.ch > 0.0) {
            DrawRectangle(border_wand, 0, 0, sz.cw - 1, sz.bh - 1);
            DrawRectangle(border_wand, 0, 0, sz.bw - 1, sz.ch - 1);
            DrawRectangle(border_wand, 0, sz.ch - sz.bh, sz.cw - 1, sz.ch - 1);
            DrawRectangle(border_wand, sz.cw - sz.bw, 0, sz.cw - 1, sz.ch - 1);
        } else {
            DrawRectangle(border_wand, 0, 0, sz.dw - 1, sz.bh - 1);
            DrawRectangle(border_wand, 0, 0, sz.bw - 1, sz.dh - 1);
            DrawRectangle(border_wand, 0, sz.dh - sz.bh, sz.dw - 1, sz.dh - 1);
            DrawRectangle(border_wand, sz.dw - sz.bw, 0, sz.dw - 1, sz.dh - 1);
        }
        MagickDrawImage(ictx->wand, border_wand);
        DestroyPixelWand(border_color);
        DestroyDrawingWand(border_wand);
    }

    /* embed icon */
    embedicon = NGX_HTTP_SMALL_LIGHT_PARAM_GET_LIT(&ctx->hash, "embedicon");
    if (ctx->material_dir->len > 0 && ngx_strlen(embedicon) > 0) {
        if (ngx_strstrn((u_char *)embedicon, "/", 1 - 1)) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "invalid parameter 'embedicon':%s %s:%d",
                          embedicon,
                          __FUNCTION__,
                          __LINE__);
            DestroyString(of_orig);
            return NGX_ERROR;
        }

        embedicon_len      = ngx_strlen(embedicon);
        embedicon_path_len = ctx->material_dir->len + ngx_strlen("/") + embedicon_len;
        if (embedicon_path_len > sizeof(embedicon_path) - 1) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "embedicon path is too long. maximun value is %z %s:%d",
                          sizeof(embedicon_path) - 1,
                          __FUNCTION__,
                          __LINE__);
            DestroyString(of_orig);
            return NGX_ERROR;
        }

        p = embedicon_path;
        p = ngx_cpystrn(p, ctx->material_dir->data, ctx->material_dir->len + 1);
        p = ngx_cpystrn(p, (u_char *)"/", 1 + 1);
        p = ngx_cpystrn(p, embedicon, embedicon_len + 1);

        if ((fd = ngx_open_file(embedicon_path, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0)) == NGX_INVALID_FILE) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "failed to open embeddedicon file:%s %s:%d",
                          embedicon_path,
                          __FUNCTION__,
                          __LINE__);
            DestroyString(of_orig);
            return NGX_ERROR;
        }

        if (ngx_close_file(fd) == NGX_FILE_ERROR) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "failed to close:%s %s:%d",
                          embedicon_path,
                          __FUNCTION__,
                          __LINE__);
            DestroyString(of_orig);
            return NGX_ERROR;
        }

        if (ngx_strstrn(embedicon_path, "..", 2 - 1)) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "invalid embeddedicon_path:%s %s:%d",
                          embedicon_path,
                          __FUNCTION__,
                          __LINE__);
            DestroyString(of_orig);
            return NGX_ERROR;
        }

        icon_wand = NewMagickWand();
        if (MagickReadImage(icon_wand, (char *)embedicon_path) == MagickFalse) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "failed to read embed icon image file:%s %s:%d",
                          embedicon_path,
                          __FUNCTION__,
                          __LINE__);
            DestroyMagickWand(icon_wand);
            DestroyString(of_orig);
            return NGX_ERROR;
        }

        MagickCompositeImageChannel(ictx->wand, AllChannels, icon_wand, OverCompositeOp, sz.ix, sz.iy);
        DestroyMagickWand(icon_wand);
    }

    /* set params. */
    q = ngx_http_small_light_parse_double(NGX_HTTP_SMALL_LIGHT_PARAM_GET_LIT(&ctx->hash, "q"));
    if (q > 0.0) {
        MagickSetImageCompressionQuality(ictx->wand, q);
    }

    progressive_flg = ngx_http_small_light_parse_flag(NGX_HTTP_SMALL_LIGHT_PARAM_GET_LIT(&ctx->hash, "progressive"));
    if (progressive_flg != 0) {
        MagickSetInterlaceScheme(ictx->wand, LineInterlace);
    }

    of = NGX_HTTP_SMALL_LIGHT_PARAM_GET_LIT(&ctx->hash, "of");
    if (ngx_strlen(of) > 0) {
        type = ngx_http_small_light_type(of);
        if (type == NGX_HTTP_SMALL_LIGHT_IMAGE_NONE) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "of is invalid(%s) %s:%d",
                          of,
                          __FUNCTION__,
                          __LINE__);
            of = (char *)ngx_http_small_light_image_exts[ictx->type - 1];
        } else if (type == NGX_HTTP_SMALL_LIGHT_IMAGE_WEBP) {
#if defined(MAGICKCORE_WEBP_DELEGATE)
            ictx->type = type;
#else
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "WebP is not supported %s:%d",
                          __FUNCTION__,
                          __LINE__);
            of = (char *)ngx_http_small_light_image_exts[ictx->type - 1];
#endif
        } else {
            ictx->type = type;
        }
        MagickSetFormat(ictx->wand, of);
        ctx->of = ngx_http_small_light_image_types[ictx->type - 1];
    } else {
        MagickSetFormat(ictx->wand, of_orig);
        ctx->of = ctx->inf;
    }

    DestroyString(of_orig);

    ctx->content        = MagickGetImageBlob(ictx->wand, &sled_image_size);
    ctx->content_length = sled_image_size;

    ngx_pfree(r->pool, ctx->content_orig);

    ictx->complete = 1;

    return NGX_OK;
}
ngx_int_t ngx_http_small_light_imagemagick_process(ngx_http_request_t *r, ngx_http_small_light_ctx_t *ctx)
{
    ngx_http_small_light_imagemagick_ctx_t *ictx;
    ngx_http_small_light_image_size_t       sz;
    MagickBooleanType                       status;
    int                                     rmprof_flg, progressive_flg;
    double                                  iw, ih, q;
    char                                   *jpeg_size_opt, *of_orig, *crop_geo, *size_geo;
    char                                   *unsharp, *sharpen, *blur, *dealpha, *of;
    MagickWand                             *trans_wand, *canvas_wand;
    DrawingWand                            *border_wand;
    PixelWand                              *bg_color, *canvas_color, *border_color;
    GeometryInfo                            geo;
    ngx_fd_t                                fd;
    MagickWand                             *icon_wand;
    u_char                                 *p, *embedicon, *embedicon_path;
    size_t                                  embedicon_path_len, embedicon_len, sled_image_size;
    ngx_int_t                               type;

    status = MagickFalse;

    ictx = (ngx_http_small_light_imagemagick_ctx_t *)ctx->ictx;

    /* adjust image size */
    ngx_http_small_light_calc_image_size(r, ctx, &sz, 10000.0, 10000.0);

    /* init */
    ictx->wand = NewMagickWand();

    /* prepare */
    if (sz.jpeghint_flg != 0) {
        jpeg_size_opt = ngx_pcalloc(r->pool, 32 + 1);
        if (jpeg_size_opt == NULL) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "failed to allocate memory from r->pool %s:%d",
                          __FUNCTION__,
                          __LINE__);
            return NGX_ERROR;
        }
        ngx_snprintf((u_char *)jpeg_size_opt, 32 + 1, "%dx%d", (ngx_int_t)sz.dw, (ngx_int_t)sz.dh);
        MagickSetOption(ictx->wand, "jpeg:size", jpeg_size_opt);
    }

    /* load image. */
    status = MagickReadImageBlob(ictx->wand, (void *)ictx->image, ictx->image_len);
    if (status == MagickFalse) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "couldn't read image %s:%d",
                      __FUNCTION__,
                      __LINE__);
        return NGX_ERROR;
    }

    /* remove all profiles */
    rmprof_flg = ngx_http_small_light_parse_flag(NGX_HTTP_SMALL_LIGHT_PARAM_GET_LIT(&ctx->hash, "rmprof"));
    if (rmprof_flg != 0) {
        status = MagickProfileImage(ictx->wand, "*", NULL, 0);
        if (status == MagickFalse) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "couldn't profiling image %s:%d",
                          __FUNCTION__,
                          __LINE__);
        }
    }

    /* calc size. */
    iw = (double)MagickGetImageWidth(ictx->wand);
    ih = (double)MagickGetImageHeight(ictx->wand);
    ngx_http_small_light_calc_image_size(r, ctx, &sz, iw, ih);

    /* pass through. */
    if (sz.pt_flg != 0) {
        return NGX_OK;
    }

    of_orig = MagickGetImageFormat(ictx->wand);

    /* crop, scale. */
    status = MagickTrue;
    if (sz.scale_flg != 0) {
        crop_geo = ngx_pcalloc(r->pool, 128 + 1);
        if (crop_geo == NULL) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "failed to allocate memory from r->pool %s:%d",
                          __FUNCTION__,
                          __LINE__);
            return NGX_ERROR;
        }
        size_geo = ngx_pcalloc(r->pool, 128 + 1);
        if (size_geo == NULL) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "failed to allocate memory from r->pool %s:%d",
                          __FUNCTION__,
                          __LINE__);
            return NGX_ERROR;
        }
        ngx_snprintf((u_char *)crop_geo, 128 + 1, "%f!x%f!+%f+%f", sz.sw, sz.sh, sz.sx, sz.sy);
        ngx_snprintf((u_char *)size_geo, 128 + 1, "%f!x%f!",       sz.dw, sz.dh);
        trans_wand = MagickTransformImage(ictx->wand, crop_geo, size_geo);
        if (trans_wand == NULL || trans_wand == ictx->wand) {
            r->err_status = NGX_HTTP_INTERNAL_SERVER_ERROR;
            return NGX_ERROR;
        }
        DestroyMagickWand(ictx->wand);
        ictx->wand = trans_wand;
    }

    /* rotate */
    if (sz.angle) {
        bg_color = NewPixelWand();
        PixelSetRed(bg_color,   sz.cc.r / 255.0);
        PixelSetGreen(bg_color, sz.cc.g / 255.0);
        PixelSetBlue(bg_color,  sz.cc.b / 255.0);
        PixelSetAlpha(bg_color, sz.cc.a / 255.0);

        switch (sz.angle) {
        case 90:
        case 180:
        case 270:
            MagickRotateImage(ictx->wand, bg_color, sz.angle);
            break;
        }

        DestroyPixelWand(bg_color);
    }

    /* create canvas then draw image to the canvas. */
    if (sz.cw > 0.0 && sz.ch > 0.0) {
        canvas_wand  = NewMagickWand();
        canvas_color = NewPixelWand();
        PixelSetRed(canvas_color,   sz.cc.r / 255.0);
        PixelSetGreen(canvas_color, sz.cc.g / 255.0);
        PixelSetBlue(canvas_color,  sz.cc.b / 255.0);
        PixelSetAlpha(canvas_color, sz.cc.a / 255.0);
        status = MagickNewImage(canvas_wand, sz.cw, sz.ch, canvas_color);
        DestroyPixelWand(canvas_color);
        if (status == MagickFalse) {
            r->err_status = NGX_HTTP_INTERNAL_SERVER_ERROR;
            return NGX_ERROR;
        }
        status = MagickCompositeImage(canvas_wand, ictx->wand, AtopCompositeOp, sz.dx, sz.dy);
        if (status == MagickFalse) {
            r->err_status = NGX_HTTP_INTERNAL_SERVER_ERROR;
            return NGX_ERROR;
        }
        DestroyMagickWand(ictx->wand);
        ictx->wand = canvas_wand;
    }

    /* effects. */
    unsharp = NGX_HTTP_SMALL_LIGHT_PARAM_GET_LIT(&ctx->hash, "unsharp");
    if (unsharp != NULL) {
        ParseGeometry(unsharp, &geo);
        status = MagickUnsharpMaskImage(ictx->wand, geo.rho, geo.sigma, geo.xi, geo.psi);
        if (status == MagickFalse) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "unsharp failed %s:%d",
                          __FUNCTION__,
                          __LINE__);
        }
    }

    sharpen = NGX_HTTP_SMALL_LIGHT_PARAM_GET_LIT(&ctx->hash, "sharpen");
    if (sharpen != NULL) {
        ParseGeometry(sharpen, &geo);
        status = MagickSharpenImage(ictx->wand, geo.rho, geo.sigma);
        if (status == MagickFalse) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "sharpen failed %s:%d",
                          __FUNCTION__,
                          __LINE__);
        }
    }

    blur = NGX_HTTP_SMALL_LIGHT_PARAM_GET_LIT(&ctx->hash, "blur");
    if (blur) {
        ParseGeometry(blur, &geo);
        status = MagickBlurImage(ictx->wand, geo.rho, geo.sigma);
        if (status == MagickFalse) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "blur failed %s:%d",
                          __FUNCTION__,
                          __LINE__);
        }
    }

    dealpha = NGX_HTTP_SMALL_LIGHT_PARAM_GET_LIT(&ctx->hash, "dealpha");
    if (dealpha != NULL) {
        status = MagickSetImageAlphaChannel(ictx->wand, DeactivateAlphaChannel);
        if (status == MagickFalse) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "dealpha failed %s:%d",
                          __FUNCTION__,
                          __LINE__);
        }
    }


    /* border. */
    if (sz.bw > 0.0 || sz.bh > 0.0) {
        border_wand = NewDrawingWand();
        border_color = NewPixelWand();
        PixelSetRed(border_color,   sz.bc.r / 255.0);
        PixelSetGreen(border_color, sz.bc.g / 255.0);
        PixelSetBlue(border_color,  sz.bc.b / 255.0);
        PixelSetAlpha(border_color, sz.bc.a / 255.0);
        DrawSetFillColor(border_wand, border_color);
        DrawSetStrokeColor(border_wand, border_color);
        DrawSetStrokeWidth(border_wand, 1);

        if (sz.cw > 0.0 && sz.ch > 0.0) {
            DrawRectangle(border_wand, 0, 0, sz.cw - 1, sz.bh - 1);
            DrawRectangle(border_wand, 0, 0, sz.bw - 1, sz.ch - 1);
            DrawRectangle(border_wand, 0, sz.ch - sz.bh, sz.cw - 1, sz.ch - 1);
            DrawRectangle(border_wand, sz.cw - sz.bw, 0, sz.cw - 1, sz.ch - 1);
        } else {
            DrawRectangle(border_wand, 0, 0, sz.dw - 1, sz.bh - 1);
            DrawRectangle(border_wand, 0, 0, sz.bw - 1, sz.dh - 1);
            DrawRectangle(border_wand, 0, sz.dh - sz.bh, sz.dw - 1, sz.dh - 1);
            DrawRectangle(border_wand, sz.dw - sz.bw, 0, sz.dw - 1, sz.dh - 1);
        }
        MagickDrawImage(ictx->wand, border_wand);
        DestroyPixelWand(border_color);
        DestroyDrawingWand(border_wand);
    }

    /* embed icon */
    embedicon = NGX_HTTP_SMALL_LIGHT_PARAM_GET_LIT(&ctx->hash, "embedicon");
    if (ngx_strlen(ctx->material_dir) > 0 && ngx_strlen(embedicon) > 0) {
        if (ngx_strstrn((u_char *)embedicon, "/", 1 - 1)) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "invalid parameter 'embedicon':%s %s:%d",
                          embedicon,
                          __FUNCTION__,
                          __LINE__);
            return NGX_ERROR;
        }

        icon_wand = NewMagickWand();

        embedicon_len      = ngx_strlen(embedicon);
        embedicon_path_len = ctx->material_dir->len + ngx_strlen("/") + embedicon_len;
        embedicon_path     = ngx_palloc(r->pool, embedicon_path_len + 1);
        if (embedicon_path == NULL) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "failed to allocate memory from r->pool %s:%d",
                          __FUNCTION__,
                          __LINE__);
            return NGX_ERROR;
        }

        p = embedicon_path;
        p = ngx_cpystrn(p, ctx->material_dir->data, ctx->material_dir->len + 1);
        p = ngx_cpystrn(p, (u_char *)"/", 1 + 1);
        p = ngx_cpystrn(p, embedicon, embedicon_len + 1);

        if ((fd = ngx_open_file(embedicon_path, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0)) == NGX_INVALID_FILE) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "failed to open embeddedicon file:%s %s:%d",
                          embedicon_path,
                          __FUNCTION__,
                          __LINE__);
            return NGX_ERROR;
        }

        if (ngx_close_file(fd) == NGX_FILE_ERROR) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "failed to close:%s %s:%d",
                          embedicon_path,
                          __FUNCTION__,
                          __LINE__);
            return NGX_ERROR;
        }

        if (ngx_strstrn(embedicon_path, "..", 2 - 1)) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "invalid embeddedicon_path:%s %s:%d",
                          embedicon_path,
                          __FUNCTION__,
                          __LINE__);
            return NGX_ERROR;
        }

        if (MagickReadImage(icon_wand, (char *)embedicon_path) == MagickFalse) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "failed to read embed icon image file:%s %s:%d",
                          embedicon_path,
                          __FUNCTION__,
                          __LINE__);
            return NGX_ERROR;
        }

        MagickCompositeImageChannel(ictx->wand, AllChannels, icon_wand, OverCompositeOp, sz.ix, sz.iy);
        ClearMagickWand(icon_wand);
    }

    /* set params. */
    q = ngx_http_small_light_parse_double(NGX_HTTP_SMALL_LIGHT_PARAM_GET_LIT(&ctx->hash, "q"));
    if (q > 0.0) {
        MagickSetImageCompressionQuality(ictx->wand, q);
    }

    progressive_flg = ngx_http_small_light_parse_flag(NGX_HTTP_SMALL_LIGHT_PARAM_GET_LIT(&ctx->hash, "progressive"));
    if (progressive_flg != 0) {
        MagickSetInterlaceScheme(ictx->wand, LineInterlace);
    }

    of = NGX_HTTP_SMALL_LIGHT_PARAM_GET_LIT(&ctx->hash, "of");
    if (ngx_strlen(of) > 0) {
        type = ngx_http_small_light_type(of);
        if (type == NGX_HTTP_SMALL_LIGHT_IMAGE_NONE) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "of is invalid(%s) %s:%d",
                          of,
                          __FUNCTION__,
                          __LINE__);
            of = (char *)ngx_http_small_light_image_exts[ictx->type - 1];
        } else if (type == NGX_HTTP_SMALL_LIGHT_IMAGE_WEBP) {
#if defined(MAGICKCORE_WEBP_DELEGATE)
            ictx->type = type;
#else
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "WebP is not supported %s:%d",
                          __FUNCTION__,
                          __LINE__);
            of = (char *)ngx_http_small_light_image_exts[ictx->type - 1];
#endif
        } else {
            ictx->type = type;
        }
        MagickSetFormat(ictx->wand, of);
        ctx->of = ngx_http_small_light_image_types[ictx->type - 1];
    } else {
        MagickSetFormat(ictx->wand, of_orig);
        ctx->of = ctx->inf;
    }

    ctx->content        = MagickGetImageBlob(ictx->wand, &sled_image_size);
    ctx->content_length = sled_image_size;

    ictx->complete = 1;

    return NGX_OK;
}