src/http/modules/ngx_http_image_filter_module.c - nginx-1.7.10

Global variables defined

Data types defined

Functions defined

Macros defined

Source code


  1. /*
  2. * Copyright (C) Igor Sysoev
  3. * Copyright (C) Nginx, Inc.
  4. */


  5. #include <ngx_config.h>
  6. #include <ngx_core.h>
  7. #include <ngx_http.h>

  8. #include <gd.h>


  9. #define NGX_HTTP_IMAGE_OFF       0
  10. #define NGX_HTTP_IMAGE_TEST      1
  11. #define NGX_HTTP_IMAGE_SIZE      2
  12. #define NGX_HTTP_IMAGE_RESIZE    3
  13. #define NGX_HTTP_IMAGE_CROP      4
  14. #define NGX_HTTP_IMAGE_ROTATE    5


  15. #define NGX_HTTP_IMAGE_START     0
  16. #define NGX_HTTP_IMAGE_READ      1
  17. #define NGX_HTTP_IMAGE_PROCESS   2
  18. #define NGX_HTTP_IMAGE_PASS      3
  19. #define NGX_HTTP_IMAGE_DONE      4


  20. #define NGX_HTTP_IMAGE_NONE      0
  21. #define NGX_HTTP_IMAGE_JPEG      1
  22. #define NGX_HTTP_IMAGE_GIF       2
  23. #define NGX_HTTP_IMAGE_PNG       3


  24. #define NGX_HTTP_IMAGE_BUFFERED  0x08


  25. typedef struct {
  26.     ngx_uint_t                   filter;
  27.     ngx_uint_t                   width;
  28.     ngx_uint_t                   height;
  29.     ngx_uint_t                   angle;
  30.     ngx_uint_t                   jpeg_quality;
  31.     ngx_uint_t                   sharpen;

  32.     ngx_flag_t                   transparency;
  33.     ngx_flag_t                   interlace;

  34.     ngx_http_complex_value_t    *wcv;
  35.     ngx_http_complex_value_t    *hcv;
  36.     ngx_http_complex_value_t    *acv;
  37.     ngx_http_complex_value_t    *jqcv;
  38.     ngx_http_complex_value_t    *shcv;

  39.     size_t                       buffer_size;
  40. } ngx_http_image_filter_conf_t;


  41. typedef struct {
  42.     u_char                      *image;
  43.     u_char                      *last;

  44.     size_t                       length;

  45.     ngx_uint_t                   width;
  46.     ngx_uint_t                   height;
  47.     ngx_uint_t                   max_width;
  48.     ngx_uint_t                   max_height;
  49.     ngx_uint_t                   angle;

  50.     ngx_uint_t                   phase;
  51.     ngx_uint_t                   type;
  52.     ngx_uint_t                   force;
  53. } ngx_http_image_filter_ctx_t;


  54. static ngx_int_t ngx_http_image_send(ngx_http_request_t *r,
  55.     ngx_http_image_filter_ctx_t *ctx, ngx_chain_t *in);
  56. static ngx_uint_t ngx_http_image_test(ngx_http_request_t *r, ngx_chain_t *in);
  57. static ngx_int_t ngx_http_image_read(ngx_http_request_t *r, ngx_chain_t *in);
  58. static ngx_buf_t *ngx_http_image_process(ngx_http_request_t *r);
  59. static ngx_buf_t *ngx_http_image_json(ngx_http_request_t *r,
  60.     ngx_http_image_filter_ctx_t *ctx);
  61. static ngx_buf_t *ngx_http_image_asis(ngx_http_request_t *r,
  62.     ngx_http_image_filter_ctx_t *ctx);
  63. static void ngx_http_image_length(ngx_http_request_t *r, ngx_buf_t *b);
  64. static ngx_int_t ngx_http_image_size(ngx_http_request_t *r,
  65.     ngx_http_image_filter_ctx_t *ctx);

  66. static ngx_buf_t *ngx_http_image_resize(ngx_http_request_t *r,
  67.     ngx_http_image_filter_ctx_t *ctx);
  68. static gdImagePtr ngx_http_image_source(ngx_http_request_t *r,
  69.     ngx_http_image_filter_ctx_t *ctx);
  70. static gdImagePtr ngx_http_image_new(ngx_http_request_t *r, int w, int h,
  71.     int colors);
  72. static u_char *ngx_http_image_out(ngx_http_request_t *r, ngx_uint_t type,
  73.     gdImagePtr img, int *size);
  74. static void ngx_http_image_cleanup(void *data);
  75. static ngx_uint_t ngx_http_image_filter_get_value(ngx_http_request_t *r,
  76.     ngx_http_complex_value_t *cv, ngx_uint_t v);
  77. static ngx_uint_t ngx_http_image_filter_value(ngx_str_t *value);


  78. static void *ngx_http_image_filter_create_conf(ngx_conf_t *cf);
  79. static char *ngx_http_image_filter_merge_conf(ngx_conf_t *cf, void *parent,
  80.     void *child);
  81. static char *ngx_http_image_filter(ngx_conf_t *cf, ngx_command_t *cmd,
  82.     void *conf);
  83. static char *ngx_http_image_filter_jpeg_quality(ngx_conf_t *cf,
  84.     ngx_command_t *cmd, void *conf);
  85. static char *ngx_http_image_filter_sharpen(ngx_conf_t *cf, ngx_command_t *cmd,
  86.     void *conf);
  87. static ngx_int_t ngx_http_image_filter_init(ngx_conf_t *cf);


  88. static ngx_command_t  ngx_http_image_filter_commands[] = {

  89.     { ngx_string("image_filter"),
  90.       NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123,
  91.       ngx_http_image_filter,
  92.       NGX_HTTP_LOC_CONF_OFFSET,
  93.       0,
  94.       NULL },

  95.     { ngx_string("image_filter_jpeg_quality"),
  96.       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
  97.       ngx_http_image_filter_jpeg_quality,
  98.       NGX_HTTP_LOC_CONF_OFFSET,
  99.       0,
  100.       NULL },

  101.     { ngx_string("image_filter_sharpen"),
  102.       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
  103.       ngx_http_image_filter_sharpen,
  104.       NGX_HTTP_LOC_CONF_OFFSET,
  105.       0,
  106.       NULL },

  107.     { ngx_string("image_filter_transparency"),
  108.       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
  109.       ngx_conf_set_flag_slot,
  110.       NGX_HTTP_LOC_CONF_OFFSET,
  111.       offsetof(ngx_http_image_filter_conf_t, transparency),
  112.       NULL },

  113.    { ngx_string("image_filter_interlace"),
  114.       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
  115.       ngx_conf_set_flag_slot,
  116.       NGX_HTTP_LOC_CONF_OFFSET,
  117.       offsetof(ngx_http_image_filter_conf_t, interlace),
  118.       NULL },

  119.     { ngx_string("image_filter_buffer"),
  120.       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
  121.       ngx_conf_set_size_slot,
  122.       NGX_HTTP_LOC_CONF_OFFSET,
  123.       offsetof(ngx_http_image_filter_conf_t, buffer_size),
  124.       NULL },

  125.       ngx_null_command
  126. };


  127. static ngx_http_module_t  ngx_http_image_filter_module_ctx = {
  128.     NULL,                                  /* preconfiguration */
  129.     ngx_http_image_filter_init,            /* postconfiguration */

  130.     NULL,                                  /* create main configuration */
  131.     NULL,                                  /* init main configuration */

  132.     NULL,                                  /* create server configuration */
  133.     NULL,                                  /* merge server configuration */

  134.     ngx_http_image_filter_create_conf,     /* create location configuration */
  135.     ngx_http_image_filter_merge_conf       /* merge location configuration */
  136. };


  137. ngx_module_t  ngx_http_image_filter_module = {
  138.     NGX_MODULE_V1,
  139.     &ngx_http_image_filter_module_ctx,     /* module context */
  140.     ngx_http_image_filter_commands,        /* module directives */
  141.     NGX_HTTP_MODULE,                       /* module type */
  142.     NULL,                                  /* init master */
  143.     NULL,                                  /* init module */
  144.     NULL,                                  /* init process */
  145.     NULL,                                  /* init thread */
  146.     NULL,                                  /* exit thread */
  147.     NULL,                                  /* exit process */
  148.     NULL,                                  /* exit master */
  149.     NGX_MODULE_V1_PADDING
  150. };


  151. static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
  152. static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;


  153. static ngx_str_t  ngx_http_image_types[] = {
  154.     ngx_string("image/jpeg"),
  155.     ngx_string("image/gif"),
  156.     ngx_string("image/png")
  157. };


  158. static ngx_int_t
  159. ngx_http_image_header_filter(ngx_http_request_t *r)
  160. {
  161.     off_t                          len;
  162.     ngx_http_image_filter_ctx_t   *ctx;
  163.     ngx_http_image_filter_conf_t  *conf;

  164.     if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) {
  165.         return ngx_http_next_header_filter(r);
  166.     }

  167.     ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);

  168.     if (ctx) {
  169.         ngx_http_set_ctx(r, NULL, ngx_http_image_filter_module);
  170.         return ngx_http_next_header_filter(r);
  171.     }

  172.     conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);

  173.     if (conf->filter == NGX_HTTP_IMAGE_OFF) {
  174.         return ngx_http_next_header_filter(r);
  175.     }

  176.     if (r->headers_out.content_type.len
  177.             >= sizeof("multipart/x-mixed-replace") - 1
  178.         && ngx_strncasecmp(r->headers_out.content_type.data,
  179.                            (u_char *) "multipart/x-mixed-replace",
  180.                            sizeof("multipart/x-mixed-replace") - 1)
  181.            == 0)
  182.     {
  183.         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
  184.                       "image filter: multipart/x-mixed-replace response");

  185.         return NGX_ERROR;
  186.     }

  187.     ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_image_filter_ctx_t));
  188.     if (ctx == NULL) {
  189.         return NGX_ERROR;
  190.     }

  191.     ngx_http_set_ctx(r, ctx, ngx_http_image_filter_module);

  192.     len = r->headers_out.content_length_n;

  193.     if (len != -1 && len > (off_t) conf->buffer_size) {
  194.         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
  195.                       "image filter: too big response: %O", len);

  196.         return NGX_HTTP_UNSUPPORTED_MEDIA_TYPE;
  197.     }

  198.     if (len == -1) {
  199.         ctx->length = conf->buffer_size;

  200.     } else {
  201.         ctx->length = (size_t) len;
  202.     }

  203.     if (r->headers_out.refresh) {
  204.         r->headers_out.refresh->hash = 0;
  205.     }

  206.     r->main_filter_need_in_memory = 1;
  207.     r->allow_ranges = 0;

  208.     return NGX_OK;
  209. }


  210. static ngx_int_t
  211. ngx_http_image_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
  212. {
  213.     ngx_int_t                      rc;
  214.     ngx_str_t                     *ct;
  215.     ngx_chain_t                    out;
  216.     ngx_http_image_filter_ctx_t   *ctx;
  217.     ngx_http_image_filter_conf_t  *conf;

  218.     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "image filter");

  219.     if (in == NULL) {
  220.         return ngx_http_next_body_filter(r, in);
  221.     }

  222.     ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);

  223.     if (ctx == NULL) {
  224.         return ngx_http_next_body_filter(r, in);
  225.     }

  226.     switch (ctx->phase) {

  227.     case NGX_HTTP_IMAGE_START:

  228.         ctx->type = ngx_http_image_test(r, in);

  229.         conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);

  230.         if (ctx->type == NGX_HTTP_IMAGE_NONE) {

  231.             if (conf->filter == NGX_HTTP_IMAGE_SIZE) {
  232.                 out.buf = ngx_http_image_json(r, NULL);

  233.                 if (out.buf) {
  234.                     out.next = NULL;
  235.                     ctx->phase = NGX_HTTP_IMAGE_DONE;

  236.                     return ngx_http_image_send(r, ctx, &out);
  237.                 }
  238.             }

  239.             return ngx_http_filter_finalize_request(r,
  240.                                               &ngx_http_image_filter_module,
  241.                                               NGX_HTTP_UNSUPPORTED_MEDIA_TYPE);
  242.         }

  243.         /* override content type */

  244.         ct = &ngx_http_image_types[ctx->type - 1];
  245.         r->headers_out.content_type_len = ct->len;
  246.         r->headers_out.content_type = *ct;
  247.         r->headers_out.content_type_lowcase = NULL;

  248.         if (conf->filter == NGX_HTTP_IMAGE_TEST) {
  249.             ctx->phase = NGX_HTTP_IMAGE_PASS;

  250.             return ngx_http_image_send(r, ctx, in);
  251.         }

  252.         ctx->phase = NGX_HTTP_IMAGE_READ;

  253.         /* fall through */

  254.     case NGX_HTTP_IMAGE_READ:

  255.         rc = ngx_http_image_read(r, in);

  256.         if (rc == NGX_AGAIN) {
  257.             return NGX_OK;
  258.         }

  259.         if (rc == NGX_ERROR) {
  260.             return ngx_http_filter_finalize_request(r,
  261.                                               &ngx_http_image_filter_module,
  262.                                               NGX_HTTP_UNSUPPORTED_MEDIA_TYPE);
  263.         }

  264.         /* fall through */

  265.     case NGX_HTTP_IMAGE_PROCESS:

  266.         out.buf = ngx_http_image_process(r);

  267.         if (out.buf == NULL) {
  268.             return ngx_http_filter_finalize_request(r,
  269.                                               &ngx_http_image_filter_module,
  270.                                               NGX_HTTP_UNSUPPORTED_MEDIA_TYPE);
  271.         }

  272.         out.next = NULL;
  273.         ctx->phase = NGX_HTTP_IMAGE_PASS;

  274.         return ngx_http_image_send(r, ctx, &out);

  275.     case NGX_HTTP_IMAGE_PASS:

  276.         return ngx_http_next_body_filter(r, in);

  277.     default: /* NGX_HTTP_IMAGE_DONE */

  278.         rc = ngx_http_next_body_filter(r, NULL);

  279.         /* NGX_ERROR resets any pending data */
  280.         return (rc == NGX_OK) ? NGX_ERROR : rc;
  281.     }
  282. }


  283. static ngx_int_t
  284. ngx_http_image_send(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx,
  285.     ngx_chain_t *in)
  286. {
  287.     ngx_int_t  rc;

  288.     rc = ngx_http_next_header_filter(r);

  289.     if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
  290.         return NGX_ERROR;
  291.     }

  292.     rc = ngx_http_next_body_filter(r, in);

  293.     if (ctx->phase == NGX_HTTP_IMAGE_DONE) {
  294.         /* NGX_ERROR resets any pending data */
  295.         return (rc == NGX_OK) ? NGX_ERROR : rc;
  296.     }

  297.     return rc;
  298. }


  299. static ngx_uint_t
  300. ngx_http_image_test(ngx_http_request_t *r, ngx_chain_t *in)
  301. {
  302.     u_char  *p;

  303.     p = in->buf->pos;

  304.     if (in->buf->last - p < 16) {
  305.         return NGX_HTTP_IMAGE_NONE;
  306.     }

  307.     ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  308.                    "image filter: \"%c%c\"", p[0], p[1]);

  309.     if (p[0] == 0xff && p[1] == 0xd8) {

  310.         /* JPEG */

  311.         return NGX_HTTP_IMAGE_JPEG;

  312.     } else if (p[0] == 'G' && p[1] == 'I' && p[2] == 'F' && p[3] == '8'
  313.                && p[5] == 'a')
  314.     {
  315.         if (p[4] == '9' || p[4] == '7') {
  316.             /* GIF */
  317.             return NGX_HTTP_IMAGE_GIF;
  318.         }

  319.     } else if (p[0] == 0x89 && p[1] == 'P' && p[2] == 'N' && p[3] == 'G'
  320.                && p[4] == 0x0d && p[5] == 0x0a && p[6] == 0x1a && p[7] == 0x0a)
  321.     {
  322.         /* PNG */

  323.         return NGX_HTTP_IMAGE_PNG;
  324.     }

  325.     return NGX_HTTP_IMAGE_NONE;
  326. }


  327. static ngx_int_t
  328. ngx_http_image_read(ngx_http_request_t *r, ngx_chain_t *in)
  329. {
  330.     u_char                       *p;
  331.     size_t                        size, rest;
  332.     ngx_buf_t                    *b;
  333.     ngx_chain_t                  *cl;
  334.     ngx_http_image_filter_ctx_t  *ctx;

  335.     ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);

  336.     if (ctx->image == NULL) {
  337.         ctx->image = ngx_palloc(r->pool, ctx->length);
  338.         if (ctx->image == NULL) {
  339.             return NGX_ERROR;
  340.         }

  341.         ctx->last = ctx->image;
  342.     }

  343.     p = ctx->last;

  344.     for (cl = in; cl; cl = cl->next) {

  345.         b = cl->buf;
  346.         size = b->last - b->pos;

  347.         ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  348.                        "image buf: %uz", size);

  349.         rest = ctx->image + ctx->length - p;

  350.         if (size > rest) {
  351.             ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
  352.                           "image filter: too big response");
  353.             return NGX_ERROR;
  354.         }

  355.         p = ngx_cpymem(p, b->pos, size);
  356.         b->pos += size;

  357.         if (b->last_buf) {
  358.             ctx->last = p;
  359.             return NGX_OK;
  360.         }
  361.     }

  362.     ctx->last = p;
  363.     r->connection->buffered |= NGX_HTTP_IMAGE_BUFFERED;

  364.     return NGX_AGAIN;
  365. }


  366. static ngx_buf_t *
  367. ngx_http_image_process(ngx_http_request_t *r)
  368. {
  369.     ngx_int_t                      rc;
  370.     ngx_http_image_filter_ctx_t   *ctx;
  371.     ngx_http_image_filter_conf_t  *conf;

  372.     r->connection->buffered &= ~NGX_HTTP_IMAGE_BUFFERED;

  373.     ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);

  374.     rc = ngx_http_image_size(r, ctx);

  375.     conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);

  376.     if (conf->filter == NGX_HTTP_IMAGE_SIZE) {
  377.         return ngx_http_image_json(r, rc == NGX_OK ? ctx : NULL);
  378.     }

  379.     ctx->angle = ngx_http_image_filter_get_value(r, conf->acv, conf->angle);

  380.     if (conf->filter == NGX_HTTP_IMAGE_ROTATE) {

  381.         if (ctx->angle != 90 && ctx->angle != 180 && ctx->angle != 270) {
  382.             return NULL;
  383.         }

  384.         return ngx_http_image_resize(r, ctx);
  385.     }

  386.     ctx->max_width = ngx_http_image_filter_get_value(r, conf->wcv, conf->width);
  387.     if (ctx->max_width == 0) {
  388.         return NULL;
  389.     }

  390.     ctx->max_height = ngx_http_image_filter_get_value(r, conf->hcv,
  391.                                                       conf->height);
  392.     if (ctx->max_height == 0) {
  393.         return NULL;
  394.     }

  395.     if (rc == NGX_OK
  396.         && ctx->width <= ctx->max_width
  397.         && ctx->height <= ctx->max_height
  398.         && ctx->angle == 0
  399.         && !ctx->force)
  400.     {
  401.         return ngx_http_image_asis(r, ctx);
  402.     }

  403.     return ngx_http_image_resize(r, ctx);
  404. }


  405. static ngx_buf_t *
  406. ngx_http_image_json(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
  407. {
  408.     size_t      len;
  409.     ngx_buf_t  *b;

  410.     b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
  411.     if (b == NULL) {
  412.         return NULL;
  413.     }

  414.     b->memory = 1;
  415.     b->last_buf = 1;

  416.     ngx_http_clean_header(r);

  417.     r->headers_out.status = NGX_HTTP_OK;
  418.     r->headers_out.content_type_len = sizeof("application/json") - 1;
  419.     ngx_str_set(&r->headers_out.content_type, "application/json");
  420.     r->headers_out.content_type_lowcase = NULL;

  421.     if (ctx == NULL) {
  422.         b->pos = (u_char *) "{}" CRLF;
  423.         b->last = b->pos + sizeof("{}" CRLF) - 1;

  424.         ngx_http_image_length(r, b);

  425.         return b;
  426.     }

  427.     len = sizeof("{ \"img\" : "
  428.                  "{ \"width\": , \"height\": , \"type\": \"jpeg\" } }" CRLF) - 1
  429.           + 2 * NGX_SIZE_T_LEN;

  430.     b->pos = ngx_pnalloc(r->pool, len);
  431.     if (b->pos == NULL) {
  432.         return NULL;
  433.     }

  434.     b->last = ngx_sprintf(b->pos,
  435.                           "{ \"img\" : "
  436.                                        "{ \"width\": %uz,"
  437.                                         " \"height\": %uz,"
  438.                                         " \"type\": \"%s\" } }" CRLF,
  439.                           ctx->width, ctx->height,
  440.                           ngx_http_image_types[ctx->type - 1].data + 6);

  441.     ngx_http_image_length(r, b);

  442.     return b;
  443. }


  444. static ngx_buf_t *
  445. ngx_http_image_asis(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
  446. {
  447.     ngx_buf_t  *b;

  448.     b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
  449.     if (b == NULL) {
  450.         return NULL;
  451.     }

  452.     b->pos = ctx->image;
  453.     b->last = ctx->last;
  454.     b->memory = 1;
  455.     b->last_buf = 1;

  456.     ngx_http_image_length(r, b);

  457.     return b;
  458. }


  459. static void
  460. ngx_http_image_length(ngx_http_request_t *r, ngx_buf_t *b)
  461. {
  462.     r->headers_out.content_length_n = b->last - b->pos;

  463.     if (r->headers_out.content_length) {
  464.         r->headers_out.content_length->hash = 0;
  465.     }

  466.     r->headers_out.content_length = NULL;
  467. }


  468. static ngx_int_t
  469. ngx_http_image_size(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
  470. {
  471.     u_char      *p, *last;
  472.     size_t       len, app;
  473.     ngx_uint_t   width, height;

  474.     p = ctx->image;

  475.     switch (ctx->type) {

  476.     case NGX_HTTP_IMAGE_JPEG:

  477.         p += 2;
  478.         last = ctx->image + ctx->length - 10;
  479.         width = 0;
  480.         height = 0;
  481.         app = 0;

  482.         while (p < last) {

  483.             if (p[0] == 0xff && p[1] != 0xff) {

  484.                 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  485.                                "JPEG: %02xd %02xd", p[0], p[1]);

  486.                 p++;

  487.                 if ((*p == 0xc0 || *p == 0xc1 || *p == 0xc2 || *p == 0xc3
  488.                      || *p == 0xc9 || *p == 0xca || *p == 0xcb)
  489.                     && (width == 0 || height == 0))
  490.                 {
  491.                     width = p[6] * 256 + p[7];
  492.                     height = p[4] * 256 + p[5];
  493.                 }

  494.                 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  495.                                "JPEG: %02xd %02xd", p[1], p[2]);

  496.                 len = p[1] * 256 + p[2];

  497.                 if (*p >= 0xe1 && *p <= 0xef) {
  498.                     /* application data, e.g., EXIF, Adobe XMP, etc. */
  499.                     app += len;
  500.                 }

  501.                 p += len;

  502.                 continue;
  503.             }

  504.             p++;
  505.         }

  506.         if (width == 0 || height == 0) {
  507.             return NGX_DECLINED;
  508.         }

  509.         if (ctx->length / 20 < app) {
  510.             /* force conversion if application data consume more than 5% */
  511.             ctx->force = 1;
  512.             ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  513.                            "app data size: %uz", app);
  514.         }

  515.         break;

  516.     case NGX_HTTP_IMAGE_GIF:

  517.         if (ctx->length < 10) {
  518.             return NGX_DECLINED;
  519.         }

  520.         width = p[7] * 256 + p[6];
  521.         height = p[9] * 256 + p[8];

  522.         break;

  523.     case NGX_HTTP_IMAGE_PNG:

  524.         if (ctx->length < 24) {
  525.             return NGX_DECLINED;
  526.         }

  527.         width = p[18] * 256 + p[19];
  528.         height = p[22] * 256 + p[23];

  529.         break;

  530.     default:

  531.         return NGX_DECLINED;
  532.     }

  533.     ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  534.                    "image size: %d x %d", width, height);

  535.     ctx->width = width;
  536.     ctx->height = height;

  537.     return NGX_OK;
  538. }


  539. static ngx_buf_t *
  540. ngx_http_image_resize(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
  541. {
  542.     int                            sx, sy, dx, dy, ox, oy, ax, ay, size,
  543.                                    colors, palette, transparent, sharpen,
  544.                                    red, green, blue, t;
  545.     u_char                        *out;
  546.     ngx_buf_t                     *b;
  547.     ngx_uint_t                     resize;
  548.     gdImagePtr                     src, dst;
  549.     ngx_pool_cleanup_t            *cln;
  550.     ngx_http_image_filter_conf_t  *conf;

  551.     src = ngx_http_image_source(r, ctx);

  552.     if (src == NULL) {
  553.         return NULL;
  554.     }

  555.     sx = gdImageSX(src);
  556.     sy = gdImageSY(src);

  557.     conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);

  558.     if (!ctx->force
  559.         && ctx->angle == 0
  560.         && (ngx_uint_t) sx <= ctx->max_width
  561.         && (ngx_uint_t) sy <= ctx->max_height)
  562.     {
  563.         gdImageDestroy(src);
  564.         return ngx_http_image_asis(r, ctx);
  565.     }

  566.     colors = gdImageColorsTotal(src);

  567.     if (colors && conf->transparency) {
  568.         transparent = gdImageGetTransparent(src);

  569.         if (transparent != -1) {
  570.             palette = colors;
  571.             red = gdImageRed(src, transparent);
  572.             green = gdImageGreen(src, transparent);
  573.             blue = gdImageBlue(src, transparent);

  574.             goto transparent;
  575.         }
  576.     }

  577.     palette = 0;
  578.     transparent = -1;
  579.     red = 0;
  580.     green = 0;
  581.     blue = 0;

  582. transparent:

  583.     gdImageColorTransparent(src, -1);

  584.     dx = sx;
  585.     dy = sy;

  586.     if (conf->filter == NGX_HTTP_IMAGE_RESIZE) {

  587.         if ((ngx_uint_t) dx > ctx->max_width) {
  588.             dy = dy * ctx->max_width / dx;
  589.             dy = dy ? dy : 1;
  590.             dx = ctx->max_width;
  591.         }

  592.         if ((ngx_uint_t) dy > ctx->max_height) {
  593.             dx = dx * ctx->max_height / dy;
  594.             dx = dx ? dx : 1;
  595.             dy = ctx->max_height;
  596.         }

  597.         resize = 1;

  598.     } else if (conf->filter == NGX_HTTP_IMAGE_ROTATE) {

  599.         resize = 0;

  600.     } else { /* NGX_HTTP_IMAGE_CROP */

  601.         resize = 0;

  602.         if ((double) dx / dy < (double) ctx->max_width / ctx->max_height) {
  603.             if ((ngx_uint_t) dx > ctx->max_width) {
  604.                 dy = dy * ctx->max_width / dx;
  605.                 dy = dy ? dy : 1;
  606.                 dx = ctx->max_width;
  607.                 resize = 1;
  608.             }

  609.         } else {
  610.             if ((ngx_uint_t) dy > ctx->max_height) {
  611.                 dx = dx * ctx->max_height / dy;
  612.                 dx = dx ? dx : 1;
  613.                 dy = ctx->max_height;
  614.                 resize = 1;
  615.             }
  616.         }
  617.     }

  618.     if (resize) {
  619.         dst = ngx_http_image_new(r, dx, dy, palette);
  620.         if (dst == NULL) {
  621.             gdImageDestroy(src);
  622.             return NULL;
  623.         }

  624.         if (colors == 0) {
  625.             gdImageSaveAlpha(dst, 1);
  626.             gdImageAlphaBlending(dst, 0);
  627.         }

  628.         gdImageCopyResampled(dst, src, 0, 0, 0, 0, dx, dy, sx, sy);

  629.         if (colors) {
  630.             gdImageTrueColorToPalette(dst, 1, 256);
  631.         }

  632.         gdImageDestroy(src);

  633.     } else {
  634.         dst = src;
  635.     }

  636.     if (ctx->angle) {
  637.         src = dst;

  638.         ax = (dx % 2 == 0) ? 1 : 0;
  639.         ay = (dy % 2 == 0) ? 1 : 0;

  640.         switch (ctx->angle) {

  641.         case 90:
  642.         case 270:
  643.             dst = ngx_http_image_new(r, dy, dx, palette);
  644.             if (dst == NULL) {
  645.                 gdImageDestroy(src);
  646.                 return NULL;
  647.             }
  648.             if (ctx->angle == 90) {
  649.                 ox = dy / 2 + ay;
  650.                 oy = dx / 2 - ax;

  651.             } else {
  652.                 ox = dy / 2 - ay;
  653.                 oy = dx / 2 + ax;
  654.             }

  655.             gdImageCopyRotated(dst, src, ox, oy, 0, 0,
  656.                                dx + ax, dy + ay, ctx->angle);
  657.             gdImageDestroy(src);

  658.             t = dx;
  659.             dx = dy;
  660.             dy = t;
  661.             break;

  662.         case 180:
  663.             dst = ngx_http_image_new(r, dx, dy, palette);
  664.             if (dst == NULL) {
  665.                 gdImageDestroy(src);
  666.                 return NULL;
  667.             }
  668.             gdImageCopyRotated(dst, src, dx / 2 - ax, dy / 2 - ay, 0, 0,
  669.                                dx + ax, dy + ay, ctx->angle);
  670.             gdImageDestroy(src);
  671.             break;
  672.         }
  673.     }

  674.     if (conf->filter == NGX_HTTP_IMAGE_CROP) {

  675.         src = dst;

  676.         if ((ngx_uint_t) dx > ctx->max_width) {
  677.             ox = dx - ctx->max_width;

  678.         } else {
  679.             ox = 0;
  680.         }

  681.         if ((ngx_uint_t) dy > ctx->max_height) {
  682.             oy = dy - ctx->max_height;

  683.         } else {
  684.             oy = 0;
  685.         }

  686.         if (ox || oy) {

  687.             dst = ngx_http_image_new(r, dx - ox, dy - oy, colors);

  688.             if (dst == NULL) {
  689.                 gdImageDestroy(src);
  690.                 return NULL;
  691.             }

  692.             ox /= 2;
  693.             oy /= 2;

  694.             ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  695.                            "image crop: %d x %d @ %d x %d",
  696.                            dx, dy, ox, oy);

  697.             if (colors == 0) {
  698.                 gdImageSaveAlpha(dst, 1);
  699.                 gdImageAlphaBlending(dst, 0);
  700.             }

  701.             gdImageCopy(dst, src, 0, 0, ox, oy, dx - ox, dy - oy);

  702.             if (colors) {
  703.                 gdImageTrueColorToPalette(dst, 1, 256);
  704.             }

  705.             gdImageDestroy(src);
  706.         }
  707.     }

  708.     if (transparent != -1 && colors) {
  709.         gdImageColorTransparent(dst, gdImageColorExact(dst, red, green, blue));
  710.     }

  711.     sharpen = ngx_http_image_filter_get_value(r, conf->shcv, conf->sharpen);
  712.     if (sharpen > 0) {
  713.         gdImageSharpen(dst, sharpen);
  714.     }

  715.     gdImageInterlace(dst, (int) conf->interlace);

  716.     out = ngx_http_image_out(r, ctx->type, dst, &size);

  717.     ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  718.                    "image: %d x %d %d", sx, sy, colors);

  719.     gdImageDestroy(dst);
  720.     ngx_pfree(r->pool, ctx->image);

  721.     if (out == NULL) {
  722.         return NULL;
  723.     }

  724.     cln = ngx_pool_cleanup_add(r->pool, 0);
  725.     if (cln == NULL) {
  726.         gdFree(out);
  727.         return NULL;
  728.     }

  729.     b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
  730.     if (b == NULL) {
  731.         gdFree(out);
  732.         return NULL;
  733.     }

  734.     cln->handler = ngx_http_image_cleanup;
  735.     cln->data = out;

  736.     b->pos = out;
  737.     b->last = out + size;
  738.     b->memory = 1;
  739.     b->last_buf = 1;

  740.     ngx_http_image_length(r, b);
  741.     ngx_http_weak_etag(r);

  742.     return b;
  743. }


  744. static gdImagePtr
  745. ngx_http_image_source(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
  746. {
  747.     char        *failed;
  748.     gdImagePtr   img;

  749.     img = NULL;

  750.     switch (ctx->type) {

  751.     case NGX_HTTP_IMAGE_JPEG:
  752.         img = gdImageCreateFromJpegPtr(ctx->length, ctx->image);
  753.         failed = "gdImageCreateFromJpegPtr() failed";
  754.         break;

  755.     case NGX_HTTP_IMAGE_GIF:
  756.         img = gdImageCreateFromGifPtr(ctx->length, ctx->image);
  757.         failed = "gdImageCreateFromGifPtr() failed";
  758.         break;

  759.     case NGX_HTTP_IMAGE_PNG:
  760.         img = gdImageCreateFromPngPtr(ctx->length, ctx->image);
  761.         failed = "gdImageCreateFromPngPtr() failed";
  762.         break;

  763.     default:
  764.         failed = "unknown image type";
  765.         break;
  766.     }

  767.     if (img == NULL) {
  768.         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, failed);
  769.     }

  770.     return img;
  771. }


  772. static gdImagePtr
  773. ngx_http_image_new(ngx_http_request_t *r, int w, int h, int colors)
  774. {
  775.     gdImagePtr  img;

  776.     if (colors == 0) {
  777.         img = gdImageCreateTrueColor(w, h);

  778.         if (img == NULL) {
  779.             ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
  780.                           "gdImageCreateTrueColor() failed");
  781.             return NULL;
  782.         }

  783.     } else {
  784.         img = gdImageCreate(w, h);

  785.         if (img == NULL) {
  786.             ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
  787.                           "gdImageCreate() failed");
  788.             return NULL;
  789.         }
  790.     }

  791.     return img;
  792. }


  793. static u_char *
  794. ngx_http_image_out(ngx_http_request_t *r, ngx_uint_t type, gdImagePtr img,
  795.     int *size)
  796. {
  797.     char                          *failed;
  798.     u_char                        *out;
  799.     ngx_int_t                      jq;
  800.     ngx_http_image_filter_conf_t  *conf;

  801.     out = NULL;

  802.     switch (type) {

  803.     case NGX_HTTP_IMAGE_JPEG:
  804.         conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);

  805.         jq = ngx_http_image_filter_get_value(r, conf->jqcv, conf->jpeg_quality);
  806.         if (jq <= 0) {
  807.             return NULL;
  808.         }

  809.         out = gdImageJpegPtr(img, size, jq);
  810.         failed = "gdImageJpegPtr() failed";
  811.         break;

  812.     case NGX_HTTP_IMAGE_GIF:
  813.         out = gdImageGifPtr(img, size);
  814.         failed = "gdImageGifPtr() failed";
  815.         break;

  816.     case NGX_HTTP_IMAGE_PNG:
  817.         out = gdImagePngPtr(img, size);
  818.         failed = "gdImagePngPtr() failed";
  819.         break;

  820.     default:
  821.         failed = "unknown image type";
  822.         break;
  823.     }

  824.     if (out == NULL) {
  825.         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, failed);
  826.     }

  827.     return out;
  828. }


  829. static void
  830. ngx_http_image_cleanup(void *data)
  831. {
  832.     gdFree(data);
  833. }


  834. static ngx_uint_t
  835. ngx_http_image_filter_get_value(ngx_http_request_t *r,
  836.     ngx_http_complex_value_t *cv, ngx_uint_t v)
  837. {
  838.     ngx_str_t  val;

  839.     if (cv == NULL) {
  840.         return v;
  841.     }

  842.     if (ngx_http_complex_value(r, cv, &val) != NGX_OK) {
  843.         return 0;
  844.     }

  845.     return ngx_http_image_filter_value(&val);
  846. }


  847. static ngx_uint_t
  848. ngx_http_image_filter_value(ngx_str_t *value)
  849. {
  850.     ngx_int_t  n;

  851.     if (value->len == 1 && value->data[0] == '-') {
  852.         return (ngx_uint_t) -1;
  853.     }

  854.     n = ngx_atoi(value->data, value->len);

  855.     if (n > 0) {
  856.         return (ngx_uint_t) n;
  857.     }

  858.     return 0;
  859. }


  860. static void *
  861. ngx_http_image_filter_create_conf(ngx_conf_t *cf)
  862. {
  863.     ngx_http_image_filter_conf_t  *conf;

  864.     conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_image_filter_conf_t));
  865.     if (conf == NULL) {
  866.         return NULL;
  867.     }

  868.     /*
  869.      * set by ngx_pcalloc():
  870.      *
  871.      *     conf->width = 0;
  872.      *     conf->height = 0;
  873.      *     conf->angle = 0;
  874.      *     conf->wcv = NULL;
  875.      *     conf->hcv = NULL;
  876.      *     conf->acv = NULL;
  877.      *     conf->jqcv = NULL;
  878.      *     conf->shcv = NULL;
  879.      */

  880.     conf->filter = NGX_CONF_UNSET_UINT;
  881.     conf->jpeg_quality = NGX_CONF_UNSET_UINT;
  882.     conf->sharpen = NGX_CONF_UNSET_UINT;
  883.     conf->transparency = NGX_CONF_UNSET;
  884.     conf->interlace = NGX_CONF_UNSET;
  885.     conf->buffer_size = NGX_CONF_UNSET_SIZE;

  886.     return conf;
  887. }


  888. static char *
  889. ngx_http_image_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child)
  890. {
  891.     ngx_http_image_filter_conf_t *prev = parent;
  892.     ngx_http_image_filter_conf_t *conf = child;

  893.     if (conf->filter == NGX_CONF_UNSET_UINT) {

  894.         if (prev->filter == NGX_CONF_UNSET_UINT) {
  895.             conf->filter = NGX_HTTP_IMAGE_OFF;

  896.         } else {
  897.             conf->filter = prev->filter;
  898.             conf->width = prev->width;
  899.             conf->height = prev->height;
  900.             conf->angle = prev->angle;
  901.             conf->wcv = prev->wcv;
  902.             conf->hcv = prev->hcv;
  903.             conf->acv = prev->acv;
  904.         }
  905.     }

  906.     if (conf->jpeg_quality == NGX_CONF_UNSET_UINT) {

  907.         /* 75 is libjpeg default quality */
  908.         ngx_conf_merge_uint_value(conf->jpeg_quality, prev->jpeg_quality, 75);

  909.         if (conf->jqcv == NULL) {
  910.             conf->jqcv = prev->jqcv;
  911.         }
  912.     }

  913.     if (conf->sharpen == NGX_CONF_UNSET_UINT) {
  914.         ngx_conf_merge_uint_value(conf->sharpen, prev->sharpen, 0);

  915.         if (conf->shcv == NULL) {
  916.             conf->shcv = prev->shcv;
  917.         }
  918.     }

  919.     ngx_conf_merge_value(conf->transparency, prev->transparency, 1);

  920.     ngx_conf_merge_value(conf->interlace, prev->interlace, 0);

  921.     ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size,
  922.                               1 * 1024 * 1024);

  923.     return NGX_CONF_OK;
  924. }


  925. static char *
  926. ngx_http_image_filter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
  927. {
  928.     ngx_http_image_filter_conf_t *imcf = conf;

  929.     ngx_str_t                         *value;
  930.     ngx_int_t                          n;
  931.     ngx_uint_t                         i;
  932.     ngx_http_complex_value_t           cv;
  933.     ngx_http_compile_complex_value_t   ccv;

  934.     value = cf->args->elts;

  935.     i = 1;

  936.     if (cf->args->nelts == 2) {
  937.         if (ngx_strcmp(value[i].data, "off") == 0) {
  938.             imcf->filter = NGX_HTTP_IMAGE_OFF;

  939.         } else if (ngx_strcmp(value[i].data, "test") == 0) {
  940.             imcf->filter = NGX_HTTP_IMAGE_TEST;

  941.         } else if (ngx_strcmp(value[i].data, "size") == 0) {
  942.             imcf->filter = NGX_HTTP_IMAGE_SIZE;

  943.         } else {
  944.             goto failed;
  945.         }

  946.         return NGX_CONF_OK;

  947.     } else if (cf->args->nelts == 3) {

  948.         if (ngx_strcmp(value[i].data, "rotate") == 0) {
  949.             if (imcf->filter != NGX_HTTP_IMAGE_RESIZE
  950.                 && imcf->filter != NGX_HTTP_IMAGE_CROP)
  951.             {
  952.                 imcf->filter = NGX_HTTP_IMAGE_ROTATE;
  953.             }

  954.             ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));

  955.             ccv.cf = cf;
  956.             ccv.value = &value[++i];
  957.             ccv.complex_value = &cv;

  958.             if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
  959.                 return NGX_CONF_ERROR;
  960.             }

  961.             if (cv.lengths == NULL) {
  962.                 n = ngx_http_image_filter_value(&value[i]);

  963.                 if (n != 90 && n != 180 && n != 270) {
  964.                     goto failed;
  965.                 }

  966.                 imcf->angle = (ngx_uint_t) n;

  967.             } else {
  968.                 imcf->acv = ngx_palloc(cf->pool,
  969.                                        sizeof(ngx_http_complex_value_t));
  970.                 if (imcf->acv == NULL) {
  971.                     return NGX_CONF_ERROR;
  972.                 }

  973.                 *imcf->acv = cv;
  974.             }

  975.             return NGX_CONF_OK;

  976.         } else {
  977.             goto failed;
  978.         }
  979.     }

  980.     if (ngx_strcmp(value[i].data, "resize") == 0) {
  981.         imcf->filter = NGX_HTTP_IMAGE_RESIZE;

  982.     } else if (ngx_strcmp(value[i].data, "crop") == 0) {
  983.         imcf->filter = NGX_HTTP_IMAGE_CROP;

  984.     } else {
  985.         goto failed;
  986.     }

  987.     ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));

  988.     ccv.cf = cf;
  989.     ccv.value = &value[++i];
  990.     ccv.complex_value = &cv;

  991.     if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
  992.         return NGX_CONF_ERROR;
  993.     }

  994.     if (cv.lengths == NULL) {
  995.         n = ngx_http_image_filter_value(&value[i]);

  996.         if (n == 0) {
  997.             goto failed;
  998.         }

  999.         imcf->width = (ngx_uint_t) n;

  1000.     } else {
  1001.         imcf->wcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
  1002.         if (imcf->wcv == NULL) {
  1003.             return NGX_CONF_ERROR;
  1004.         }

  1005.         *imcf->wcv = cv;
  1006.     }

  1007.     ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));

  1008.     ccv.cf = cf;
  1009.     ccv.value = &value[++i];
  1010.     ccv.complex_value = &cv;

  1011.     if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
  1012.         return NGX_CONF_ERROR;
  1013.     }

  1014.     if (cv.lengths == NULL) {
  1015.         n = ngx_http_image_filter_value(&value[i]);

  1016.         if (n == 0) {
  1017.             goto failed;
  1018.         }

  1019.         imcf->height = (ngx_uint_t) n;

  1020.     } else {
  1021.         imcf->hcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
  1022.         if (imcf->hcv == NULL) {
  1023.             return NGX_CONF_ERROR;
  1024.         }

  1025.         *imcf->hcv = cv;
  1026.     }

  1027.     return NGX_CONF_OK;

  1028. failed:

  1029.     ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"",
  1030.                        &value[i]);

  1031.     return NGX_CONF_ERROR;
  1032. }


  1033. static char *
  1034. ngx_http_image_filter_jpeg_quality(ngx_conf_t *cf, ngx_command_t *cmd,
  1035.     void *conf)
  1036. {
  1037.     ngx_http_image_filter_conf_t *imcf = conf;

  1038.     ngx_str_t                         *value;
  1039.     ngx_int_t                          n;
  1040.     ngx_http_complex_value_t           cv;
  1041.     ngx_http_compile_complex_value_t   ccv;

  1042.     value = cf->args->elts;

  1043.     ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));

  1044.     ccv.cf = cf;
  1045.     ccv.value = &value[1];
  1046.     ccv.complex_value = &cv;

  1047.     if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
  1048.         return NGX_CONF_ERROR;
  1049.     }

  1050.     if (cv.lengths == NULL) {
  1051.         n = ngx_http_image_filter_value(&value[1]);

  1052.         if (n <= 0) {
  1053.             ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
  1054.                                "invalid value \"%V\"", &value[1]);
  1055.             return NGX_CONF_ERROR;
  1056.         }

  1057.         imcf->jpeg_quality = (ngx_uint_t) n;

  1058.     } else {
  1059.         imcf->jqcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
  1060.         if (imcf->jqcv == NULL) {
  1061.             return NGX_CONF_ERROR;
  1062.         }

  1063.         *imcf->jqcv = cv;
  1064.     }

  1065.     return NGX_CONF_OK;
  1066. }


  1067. static char *
  1068. ngx_http_image_filter_sharpen(ngx_conf_t *cf, ngx_command_t *cmd,
  1069.     void *conf)
  1070. {
  1071.     ngx_http_image_filter_conf_t *imcf = conf;

  1072.     ngx_str_t                         *value;
  1073.     ngx_int_t                          n;
  1074.     ngx_http_complex_value_t           cv;
  1075.     ngx_http_compile_complex_value_t   ccv;

  1076.     value = cf->args->elts;

  1077.     ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));

  1078.     ccv.cf = cf;
  1079.     ccv.value = &value[1];
  1080.     ccv.complex_value = &cv;

  1081.     if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
  1082.         return NGX_CONF_ERROR;
  1083.     }

  1084.     if (cv.lengths == NULL) {
  1085.         n = ngx_http_image_filter_value(&value[1]);

  1086.         if (n < 0) {
  1087.             ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
  1088.                                "invalid value \"%V\"", &value[1]);
  1089.             return NGX_CONF_ERROR;
  1090.         }

  1091.         imcf->sharpen = (ngx_uint_t) n;

  1092.     } else {
  1093.         imcf->shcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
  1094.         if (imcf->shcv == NULL) {
  1095.             return NGX_CONF_ERROR;
  1096.         }

  1097.         *imcf->shcv = cv;
  1098.     }

  1099.     return NGX_CONF_OK;
  1100. }


  1101. static ngx_int_t
  1102. ngx_http_image_filter_init(ngx_conf_t *cf)
  1103. {
  1104.     ngx_http_next_header_filter = ngx_http_top_header_filter;
  1105.     ngx_http_top_header_filter = ngx_http_image_header_filter;

  1106.     ngx_http_next_body_filter = ngx_http_top_body_filter;
  1107.     ngx_http_top_body_filter = ngx_http_image_body_filter;

  1108.     return NGX_OK;
  1109. }