text: 
  /*
   * Copyright (C) Igor Sysoev
   * Copyright (C) Nginx, Inc.
   */
  
  
  #include <ngx_config.h>
  #include <ngx_core.h>
  #include <ngx_http.h>
  
  #include <gd.h>
  
  
  #define NGX_HTTP_IMAGE_OFF       0
  #define NGX_HTTP_IMAGE_TEST      1
  #define NGX_HTTP_IMAGE_SIZE      2
  #define NGX_HTTP_IMAGE_RESIZE    3
  #define NGX_HTTP_IMAGE_CROP      4
  #define NGX_HTTP_IMAGE_ROTATE    5
  #define NGX_HTTP_IMAGE_WATERMARK 6
  
  #define NGX_HTTP_IMAGE_START     0
  #define NGX_HTTP_IMAGE_READ      1
  #define NGX_HTTP_IMAGE_PROCESS   2
  #define NGX_HTTP_IMAGE_PASS      3
  #define NGX_HTTP_IMAGE_DONE      4
  
  
  #define NGX_HTTP_IMAGE_NONE      0
  #define NGX_HTTP_IMAGE_JPEG      1
  #define NGX_HTTP_IMAGE_GIF       2
  #define NGX_HTTP_IMAGE_PNG       3
  #define NGX_HTTP_IMAGE_WEBP      4
  
  
  #define NGX_HTTP_IMAGE_BUFFERED  0x08
  
  
  typedef struct {
      ngx_uint_t                   filter;
      ngx_uint_t                   width;
      ngx_uint_t                   height;
      ngx_uint_t                   angle;
      ngx_uint_t                   jpeg_quality;
      ngx_uint_t                   webp_quality;
      ngx_uint_t                   sharpen;
  
      ngx_flag_t                   transparency;
      ngx_flag_t                   interlace;
  
      ngx_str_t           watermark;  // watermark file url
      ngx_str_t           watermark_position; // top-left|top-right|bottom-right|bottom-left
      ngx_int_t           watermark_width_from; // width from use watermark
      ngx_int_t           watermark_height_from; // height from use watermark
      ngx_int_t           watermark_relative_height; //height of watermark in percents
      ngx_int_t           watermark_relative_width; //width of watermark in percents
  
      ngx_http_complex_value_t    *wcv;
      ngx_http_complex_value_t    *hcv;
      ngx_http_complex_value_t    *acv;
      ngx_http_complex_value_t    *jqcv;
      ngx_http_complex_value_t    *wqcv;
      ngx_http_complex_value_t    *shcv;
      ngx_http_complex_value_t    *wmcv;
      ngx_http_complex_value_t    *wmpcv;
  
      size_t                       buffer_size;
  } ngx_http_image_filter_conf_t;
  
  
  typedef struct {
      u_char                      *image;
      u_char                      *last;
  
      size_t                       length;
  
      ngx_uint_t                   width;
      ngx_uint_t                   height;
      ngx_uint_t                   max_width;
      ngx_uint_t                   max_height;
      ngx_uint_t                   angle;
  
      ngx_uint_t                   phase;
      ngx_uint_t                   type;
      ngx_uint_t                   force;
  } ngx_http_image_filter_ctx_t;
  
  
  static ngx_int_t ngx_http_image_send(ngx_http_request_t *r,
      ngx_http_image_filter_ctx_t *ctx, ngx_chain_t *in);
  static ngx_uint_t ngx_http_image_test(ngx_http_request_t *r, ngx_chain_t *in);
  static ngx_int_t ngx_http_image_read(ngx_http_request_t *r, ngx_chain_t *in);
  static ngx_buf_t *ngx_http_image_process(ngx_http_request_t *r);
  static ngx_buf_t *ngx_http_image_json(ngx_http_request_t *r,
      ngx_http_image_filter_ctx_t *ctx);
  static ngx_buf_t *ngx_http_image_asis(ngx_http_request_t *r,
      ngx_http_image_filter_ctx_t *ctx);
  static void ngx_http_image_length(ngx_http_request_t *r, ngx_buf_t *b);
  static ngx_int_t ngx_http_image_size(ngx_http_request_t *r,
      ngx_http_image_filter_ctx_t *ctx);
  
  static ngx_buf_t *ngx_http_image_resize(ngx_http_request_t *r,
      ngx_http_image_filter_ctx_t *ctx);
  static gdImagePtr ngx_http_image_source(ngx_http_request_t *r,
      ngx_http_image_filter_ctx_t *ctx);
  static gdImagePtr ngx_http_image_new(ngx_http_request_t *r, int w, int h,
      int colors);
  static u_char *ngx_http_image_out(ngx_http_request_t *r, ngx_uint_t type,
      gdImagePtr img, int *size);
  static void ngx_http_image_cleanup(void *data);
  static ngx_uint_t ngx_http_image_filter_get_value(ngx_http_request_t *r,
      ngx_http_complex_value_t *cv, ngx_uint_t v);
  static ngx_uint_t ngx_http_image_filter_value(ngx_str_t *value);
  
  
  static void *ngx_http_image_filter_create_conf(ngx_conf_t *cf);
  static char *ngx_http_image_filter_merge_conf(ngx_conf_t *cf, void *parent,
      void *child);
  static char *ngx_http_image_filter(ngx_conf_t *cf, ngx_command_t *cmd,
      void *conf);
  static char *ngx_http_image_filter_jpeg_quality(ngx_conf_t *cf,
      ngx_command_t *cmd, void *conf);
  static char *ngx_http_image_filter_webp_quality(ngx_conf_t *cf,
      ngx_command_t *cmd, void *conf);
  static char *ngx_http_image_filter_sharpen(ngx_conf_t *cf, ngx_command_t *cmd,
      void *conf);
  static ngx_int_t ngx_http_image_filter_init(ngx_conf_t *cf);
  
  
  static ngx_command_t  ngx_http_image_filter_commands[] = {
  
      { ngx_string("image_filter"),
        NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123,
        ngx_http_image_filter,
        NGX_HTTP_LOC_CONF_OFFSET,
        0,
        NULL },
  
      { ngx_string("image_filter_jpeg_quality"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
        ngx_http_image_filter_jpeg_quality,
        NGX_HTTP_LOC_CONF_OFFSET,
        0,
        NULL },
  
      { ngx_string("image_filter_webp_quality"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
        ngx_http_image_filter_webp_quality,
        NGX_HTTP_LOC_CONF_OFFSET,
        0,
        NULL },
  
      { ngx_string("image_filter_sharpen"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
        ngx_http_image_filter_sharpen,
        NGX_HTTP_LOC_CONF_OFFSET,
        0,
        NULL },
  
      { ngx_string("image_filter_transparency"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
        ngx_conf_set_flag_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_image_filter_conf_t, transparency),
        NULL },
  
      { ngx_string("image_filter_interlace"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
        ngx_conf_set_flag_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_image_filter_conf_t, interlace),
        NULL },
  
      { ngx_string("image_filter_buffer"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
        ngx_conf_set_size_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_image_filter_conf_t, buffer_size),
        NULL },
      { ngx_string("image_filter_watermark"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
        ngx_http_set_complex_value_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_image_filter_conf_t, wmcv),
        NULL },
      { ngx_string("image_filter_watermark_position"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
        ngx_http_set_complex_value_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_image_filter_conf_t, wmpcv),
        NULL },
      { ngx_string("image_filter_watermark_height_from"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
        ngx_conf_set_num_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_image_filter_conf_t, watermark_height_from),
        NULL },
      { ngx_string("image_filter_watermark_width_from"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
        ngx_conf_set_num_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_image_filter_conf_t, watermark_width_from),
        NULL },
      { ngx_string("image_filter_watermark_relative_height"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
        ngx_conf_set_num_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_image_filter_conf_t, watermark_relative_height),
        NULL },
      { ngx_string("image_filter_watermark_relative_width"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
        ngx_conf_set_num_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_image_filter_conf_t, watermark_relative_width),
        NULL },
  
        ngx_null_command
  };
  
  
  static ngx_http_module_t  ngx_http_image_filter_module_ctx = {
      NULL,                                  /* preconfiguration */
      ngx_http_image_filter_init,            /* postconfiguration */
  
      NULL,                                  /* create main configuration */
      NULL,                                  /* init main configuration */
  
      NULL,                                  /* create server configuration */
      NULL,                                  /* merge server configuration */
  
      ngx_http_image_filter_create_conf,     /* create location configuration */
      ngx_http_image_filter_merge_conf       /* merge location configuration */
  };
  
  
  ngx_module_t  ngx_http_image_filter_module = {
      NGX_MODULE_V1,
      &ngx_http_image_filter_module_ctx,     /* module context */
      ngx_http_image_filter_commands,        /* module directives */
      NGX_HTTP_MODULE,                       /* module type */
      NULL,                                  /* init master */
      NULL,                                  /* init module */
      NULL,                                  /* init process */
      NULL,                                  /* init thread */
      NULL,                                  /* exit thread */
      NULL,                                  /* exit process */
      NULL,                                  /* exit master */
      NGX_MODULE_V1_PADDING
  };
  
  
  static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
  static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
  
  
  static ngx_str_t  ngx_http_image_types[] = {
      ngx_string("image/jpeg"),
      ngx_string("image/gif"),
      ngx_string("image/png"),
      ngx_string("image/webp")
  };
  
  
  static ngx_int_t
  ngx_http_image_header_filter(ngx_http_request_t *r)
  {
      off_t                          len;
      ngx_http_image_filter_ctx_t   *ctx;
      ngx_http_image_filter_conf_t  *conf;
  
      if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) {
          return ngx_http_next_header_filter(r);
      }
  
      ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);
  
      if (ctx) {
          ngx_http_set_ctx(r, NULL, ngx_http_image_filter_module);
          return ngx_http_next_header_filter(r);
      }
  
      conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
  
      if (conf->filter == NGX_HTTP_IMAGE_OFF) {
          return ngx_http_next_header_filter(r);
      }
  
      if (r->headers_out.content_type.len
              >= sizeof("multipart/x-mixed-replace") - 1
          && ngx_strncasecmp(r->headers_out.content_type.data,
                             (u_char *) "multipart/x-mixed-replace",
                             sizeof("multipart/x-mixed-replace") - 1)
             == 0)
      {
          ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                        "image filter: multipart/x-mixed-replace response");
  
          return NGX_ERROR;
      }
  
      ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_image_filter_ctx_t));
      if (ctx == NULL) {
          return NGX_ERROR;
      }
  
      ngx_http_set_ctx(r, ctx, ngx_http_image_filter_module);
  
      len = r->headers_out.content_length_n;
  
      if (len != -1 && len > (off_t) conf->buffer_size) {
          ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                        "image filter: too big response: %O", len);
  
          return NGX_HTTP_UNSUPPORTED_MEDIA_TYPE;
      }
  
      if (len == -1) {
          ctx->length = conf->buffer_size;
  
      } else {
          ctx->length = (size_t) len;
      }
  
      if (r->headers_out.refresh) {
          r->headers_out.refresh->hash = 0;
      }
  
      r->main_filter_need_in_memory = 1;
      r->allow_ranges = 0;
  
      return NGX_OK;
  }
  
  
  static ngx_int_t
  ngx_http_image_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
  {
      ngx_int_t                      rc;
      ngx_str_t                     *ct;
      ngx_chain_t                    out;
      ngx_http_image_filter_ctx_t   *ctx;
      ngx_http_image_filter_conf_t  *conf;
  
      ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "image filter");
  
      if (in == NULL) {
          return ngx_http_next_body_filter(r, in);
      }
  
      ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);
  
      if (ctx == NULL) {
          return ngx_http_next_body_filter(r, in);
      }
  
      switch (ctx->phase) {
  
      case NGX_HTTP_IMAGE_START:
  
          ctx->type = ngx_http_image_test(r, in);
  
          conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
  
          if (ctx->type == NGX_HTTP_IMAGE_NONE) {
  
              if (conf->filter == NGX_HTTP_IMAGE_SIZE) {
                  out.buf = ngx_http_image_json(r, NULL);
  
                  if (out.buf) {
                      out.next = NULL;
                      ctx->phase = NGX_HTTP_IMAGE_DONE;
  
                      return ngx_http_image_send(r, ctx, &out);
                  }
              }
  
              return ngx_http_filter_finalize_request(r,
                                                &ngx_http_image_filter_module,
                                                NGX_HTTP_UNSUPPORTED_MEDIA_TYPE);
          }
  
          /* override content type */
  
          ct = &ngx_http_image_types[ctx->type - 1];
          r->headers_out.content_type_len = ct->len;
          r->headers_out.content_type = *ct;
          r->headers_out.content_type_lowcase = NULL;
  
          if (conf->filter == NGX_HTTP_IMAGE_TEST) {
              ctx->phase = NGX_HTTP_IMAGE_PASS;
  
              return ngx_http_image_send(r, ctx, in);
          }
  
          ctx->phase = NGX_HTTP_IMAGE_READ;
  
          /* fall through */
  
      case NGX_HTTP_IMAGE_READ:
  
          rc = ngx_http_image_read(r, in);
  
          if (rc == NGX_AGAIN) {
              return NGX_OK;
          }
  
          if (rc == NGX_ERROR) {
              return ngx_http_filter_finalize_request(r,
                                                &ngx_http_image_filter_module,
                                                NGX_HTTP_UNSUPPORTED_MEDIA_TYPE);
          }
  
          /* fall through */
  
      case NGX_HTTP_IMAGE_PROCESS:
  
          out.buf = ngx_http_image_process(r);
  
          if (out.buf == NULL) {
              return ngx_http_filter_finalize_request(r,
                                                &ngx_http_image_filter_module,
                                                NGX_HTTP_UNSUPPORTED_MEDIA_TYPE);
          }
  
          out.next = NULL;
          ctx->phase = NGX_HTTP_IMAGE_PASS;
  
          return ngx_http_image_send(r, ctx, &out);
  
      case NGX_HTTP_IMAGE_PASS:
  
          return ngx_http_next_body_filter(r, in);
  
      default: /* NGX_HTTP_IMAGE_DONE */
  
          rc = ngx_http_next_body_filter(r, NULL);
  
          /* NGX_ERROR resets any pending data */
          return (rc == NGX_OK) ? NGX_ERROR : rc;
      }
  }
  
  
  static ngx_int_t
  ngx_http_image_send(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx,
      ngx_chain_t *in)
  {
      ngx_int_t  rc;
  
      rc = ngx_http_next_header_filter(r);
  
      if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
          return NGX_ERROR;
      }
  
      rc = ngx_http_next_body_filter(r, in);
  
      if (ctx->phase == NGX_HTTP_IMAGE_DONE) {
          /* NGX_ERROR resets any pending data */
          return (rc == NGX_OK) ? NGX_ERROR : rc;
      }
  
      return rc;
  }
  
  
  static ngx_uint_t
  ngx_http_image_test(ngx_http_request_t *r, ngx_chain_t *in)
  {
      u_char  *p;
  
      p = in->buf->pos;
  
      if (in->buf->last - p < 16) {
          return NGX_HTTP_IMAGE_NONE;
      }
  
      ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                     "image filter: \"%c%c\"", p[0], p[1]);
  
      if (p[0] == 0xff && p[1] == 0xd8) {
  
          /* JPEG */
  
          return NGX_HTTP_IMAGE_JPEG;
  
      } else if (p[0] == 'G' && p[1] == 'I' && p[2] == 'F' && p[3] == '8'
                 && p[5] == 'a')
      {
          if (p[4] == '9' || p[4] == '7') {
              /* GIF */
              return NGX_HTTP_IMAGE_GIF;
          }
  
      } else if (p[0] == 0x89 && p[1] == 'P' && p[2] == 'N' && p[3] == 'G'
                 && p[4] == 0x0d && p[5] == 0x0a && p[6] == 0x1a && p[7] == 0x0a)
      {
          /* PNG */
  
          return NGX_HTTP_IMAGE_PNG;
  
      } else if (p[0] == 'R' && p[1] == 'I' && p[2] == 'F' && p[3] == 'F'
                 && p[8] == 'W' && p[9] == 'E' && p[10] == 'B' && p[11] == 'P')
      {
          /* WebP */
  
          return NGX_HTTP_IMAGE_WEBP;
      }
  
      return NGX_HTTP_IMAGE_NONE;
  }
  
  
  static ngx_int_t
  ngx_http_image_read(ngx_http_request_t *r, ngx_chain_t *in)
  {
      u_char                       *p;
      size_t                        size, rest;
      ngx_buf_t                    *b;
      ngx_chain_t                  *cl;
      ngx_http_image_filter_ctx_t  *ctx;
  
      ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);
  
      if (ctx->image == NULL) {
          ctx->image = ngx_palloc(r->pool, ctx->length);
          if (ctx->image == NULL) {
              return NGX_ERROR;
          }
  
          ctx->last = ctx->image;
      }
  
      p = ctx->last;
  
      for (cl = in; cl; cl = cl->next) {
  
          b = cl->buf;
          size = b->last - b->pos;
  
          ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                         "image buf: %uz", size);
  
          rest = ctx->image + ctx->length - p;
  
          if (size > rest) {
              ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                            "image filter: too big response");
              return NGX_ERROR;
          }
  
          p = ngx_cpymem(p, b->pos, size);
          b->pos += size;
  
          if (b->last_buf) {
              ctx->last = p;
              return NGX_OK;
          }
      }
  
      ctx->last = p;
      r->connection->buffered |= NGX_HTTP_IMAGE_BUFFERED;
  
      return NGX_AGAIN;
  }
  
  static ngx_str_t
  ngx_http_image_filter_get_str_value(ngx_http_request_t *r,
      ngx_http_complex_value_t *cv, ngx_str_t v)
  {
      ngx_str_t  val;
      
      if (cv == NULL) {
          return v;
      }
  
      if (ngx_http_complex_value(r, cv, &val) != NGX_OK) {
          return val;
      }
  
      return val;
  }
  
  
  static ngx_buf_t *
  ngx_http_image_process(ngx_http_request_t *r)
  {
      ngx_int_t                      rc;
      ngx_http_image_filter_ctx_t   *ctx;
      ngx_http_image_filter_conf_t  *conf;
  
      r->connection->buffered &= ~NGX_HTTP_IMAGE_BUFFERED;
  
      ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);
  
      rc = ngx_http_image_size(r, ctx);
  
      conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
  
      if (conf->filter == NGX_HTTP_IMAGE_SIZE) {
          return ngx_http_image_json(r, rc == NGX_OK ? ctx : NULL);
      }
  
      ctx->angle = ngx_http_image_filter_get_value(r, conf->acv, conf->angle);
  
      if (conf->filter == NGX_HTTP_IMAGE_ROTATE) {
  
          if (ctx->angle != 90 && ctx->angle != 180 && ctx->angle != 270) {
              return NULL;
          }
  	    return ngx_http_image_resize(r, ctx);
  	}
  
  	if (conf->wmcv || conf->watermark.data) {
  		ngx_str_t  watermark_value;
  		watermark_value = ngx_http_image_filter_get_str_value(r, conf->wmcv, conf->watermark);
  		conf->watermark.data = ngx_pcalloc(r->pool, watermark_value.len + 1);
  		ngx_cpystrn(conf->watermark.data, watermark_value.data, watermark_value.len+1);
  
  		conf->watermark.len = watermark_value.len;
  
  		ngx_str_t  watermark_position_value;
  		watermark_position_value = ngx_http_image_filter_get_str_value(r, conf->wmpcv, conf->watermark_position);
  		conf->watermark_position.data = ngx_pcalloc(r->pool, watermark_position_value.len + 1);
  		ngx_cpystrn(conf->watermark_position.data, watermark_position_value.data, watermark_position_value.len+1);
  	
  		conf->watermark_position.len = watermark_position_value.len;
  
  		if (conf->filter == NGX_HTTP_IMAGE_WATERMARK) {
  			if (!conf->watermark.data) {
  				return NULL;
  			}
  
          return ngx_http_image_resize(r, ctx);
      }
  	}
  	ctx->max_width = ngx_http_image_filter_get_value(r, conf->wcv, conf->width);
  
      if (ctx->max_width == 0) {
          return NULL;
      }
  
      ctx->max_height = ngx_http_image_filter_get_value(r, conf->hcv,
                                                        conf->height);
      if (ctx->max_height == 0) {
          return NULL;
      }
  
      if (rc == NGX_OK
          && ctx->width <= ctx->max_width
          && ctx->height <= ctx->max_height
          && ctx->angle == 0
          && !ctx->force
          && !conf->watermark.data)
      {
          return ngx_http_image_asis(r, ctx);
      }
  
      return ngx_http_image_resize(r, ctx);
  }
  
  
  static ngx_buf_t *
  ngx_http_image_json(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
  {
      size_t      len;
      ngx_buf_t  *b;
  
      b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
      if (b == NULL) {
          return NULL;
      }
  
      b->memory = 1;
      b->last_buf = 1;
  
      ngx_http_clean_header(r);
  
      r->headers_out.status = NGX_HTTP_OK;
      r->headers_out.content_type_len = sizeof("application/json") - 1;
      ngx_str_set(&r->headers_out.content_type, "application/json");
      r->headers_out.content_type_lowcase = NULL;
  
      if (ctx == NULL) {
          b->pos = (u_char *) "{}" CRLF;
          b->last = b->pos + sizeof("{}" CRLF) - 1;
  
          ngx_http_image_length(r, b);
  
          return b;
      }
  
      len = sizeof("{ \"img\" : "
                   "{ \"width\": , \"height\": , \"type\": \"jpeg\" } }" CRLF) - 1
            + 2 * NGX_SIZE_T_LEN;
  
      b->pos = ngx_pnalloc(r->pool, len);
      if (b->pos == NULL) {
          return NULL;
      }
  
      b->last = ngx_sprintf(b->pos,
                            "{ \"img\" : "
                                         "{ \"width\": %uz,"
                                          " \"height\": %uz,"
                                          " \"type\": \"%s\" } }" CRLF,
                            ctx->width, ctx->height,
                            ngx_http_image_types[ctx->type - 1].data + 6);
  
      ngx_http_image_length(r, b);
  
      return b;
  }
  
  
  static ngx_buf_t *
  ngx_http_image_asis(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
  {
      ngx_buf_t  *b;
  
      b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
      if (b == NULL) {
          return NULL;
      }
  
      b->pos = ctx->image;
      b->last = ctx->last;
      b->memory = 1;
      b->last_buf = 1;
  
      ngx_http_image_length(r, b);
  
      return b;
  }
  
  
  static void
  ngx_http_image_length(ngx_http_request_t *r, ngx_buf_t *b)
  {
      r->headers_out.content_length_n = b->last - b->pos;
  
      if (r->headers_out.content_length) {
          r->headers_out.content_length->hash = 0;
      }
  
      r->headers_out.content_length = NULL;
  }
  
  
  static ngx_int_t
  ngx_http_image_size(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
  {
      u_char      *p, *last;
      size_t       len, app;
      ngx_uint_t   width, height;
  
      p = ctx->image;
  
      switch (ctx->type) {
  
      case NGX_HTTP_IMAGE_JPEG:
  
          p += 2;
          last = ctx->image + ctx->length - 10;
          width = 0;
          height = 0;
          app = 0;
  
          while (p < last) {
  
              if (p[0] == 0xff && p[1] != 0xff) {
  
                  ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                                 "JPEG: %02xd %02xd", p[0], p[1]);
  
                  p++;
  
                  if ((*p == 0xc0 || *p == 0xc1 || *p == 0xc2 || *p == 0xc3
                       || *p == 0xc9 || *p == 0xca || *p == 0xcb)
                      && (width == 0 || height == 0))
                  {
                      width = p[6] * 256 + p[7];
                      height = p[4] * 256 + p[5];
                  }
  
                  ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                                 "JPEG: %02xd %02xd", p[1], p[2]);
  
                  len = p[1] * 256 + p[2];
  
                  if (*p >= 0xe1 && *p <= 0xef) {
                      /* application data, e.g., EXIF, Adobe XMP, etc. */
                      app += len;
                  }
  
                  p += len;
  
                  continue;
              }
  
              p++;
          }
  
          if (width == 0 || height == 0) {
              return NGX_DECLINED;
          }
  
          if (ctx->length / 20 < app) {
              /* force conversion if application data consume more than 5% */
              ctx->force = 1;
              ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                             "app data size: %uz", app);
          }
  
          break;
  
      case NGX_HTTP_IMAGE_GIF:
  
          if (ctx->length < 10) {
              return NGX_DECLINED;
          }
  
          width = p[7] * 256 + p[6];
          height = p[9] * 256 + p[8];
  
          break;
  
      case NGX_HTTP_IMAGE_PNG:
  
          if (ctx->length < 24) {
              return NGX_DECLINED;
          }
  
          width = p[18] * 256 + p[19];
          height = p[22] * 256 + p[23];
  
          break;
  
      case NGX_HTTP_IMAGE_WEBP:
  
          if (ctx->length < 30) {
              return NGX_DECLINED;
          }
  
          if (p[12] != 'V' || p[13] != 'P' || p[14] != '8') {
              return NGX_DECLINED;
          }
  
          switch (p[15]) {
  
          case ' ':
              if (p[20] & 1) {
                  /* not a key frame */
                  return NGX_DECLINED;
              }
  
              if (p[23] != 0x9d || p[24] != 0x01 || p[25] != 0x2a) {
                  /* invalid start code */
                  return NGX_DECLINED;
              }
  
              width = (p[26] | p[27] << 8) & 0x3fff;
              height = (p[28] | p[29] << 8) & 0x3fff;
  
              break;
  
          case 'L':
              if (p[20] != 0x2f) {
                  /* invalid signature */
                  return NGX_DECLINED;
              }
  
              width = ((p[21] | p[22] << 8) & 0x3fff) + 1;
              height = ((p[22] >> 6 | p[23] << 2 | p[24] << 10) & 0x3fff) + 1;
  
              break;
  
          case 'X':
              width = (p[24] | p[25] << 8 | p[26] << 16) + 1;
              height = (p[27] | p[28] << 8 | p[29] << 16) + 1;
              break;
  
          default:
              return NGX_DECLINED;
          }
  
          break;
  
      default:
  
          return NGX_DECLINED;
      }
  
      ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                     "image size: %d x %d", (int) width, (int) height);
  
      ctx->width = width;
      ctx->height = height;
  
      return NGX_OK;
  }
  
  
  static ngx_buf_t *
  ngx_http_image_resize(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
  {
      int                            sx, sy, dx, dy, ox, oy, ax, ay, size,
                                     colors, palette, transparent, sharpen,
                                     red, green, blue, t;
      u_char                        *out;
      ngx_buf_t                     *b;
      ngx_uint_t                     resize;
      gdImagePtr                     src, dst;
      ngx_pool_cleanup_t            *cln;
      ngx_http_image_filter_conf_t  *conf;
  
      src = ngx_http_image_source(r, ctx);
  
      if (src == NULL) {
          return NULL;
      }
  
      sx = gdImageSX(src);
      sy = gdImageSY(src);
  
      conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
  
      if (!ctx->force
          && ctx->angle == 0
          && (ngx_uint_t) sx <= ctx->max_width
          && (ngx_uint_t) sy <= ctx->max_height
          && !conf->watermark.data)
      {
          gdImageDestroy(src);
          return ngx_http_image_asis(r, ctx);
      }
  
      colors = gdImageColorsTotal(src);
  
      if (colors && conf->transparency) {
          transparent = gdImageGetTransparent(src);
  
          if (transparent != -1) {
              palette = colors;
              red = gdImageRed(src, transparent);
              green = gdImageGreen(src, transparent);
              blue = gdImageBlue(src, transparent);
  
              goto transparent;
          }
      }
  
      palette = 0;
      transparent = -1;
      red = 0;
      green = 0;
      blue = 0;
  
  transparent:
  
      gdImageColorTransparent(src, -1);
  
      dx = sx;
      dy = sy;
  
      if (conf->filter == NGX_HTTP_IMAGE_RESIZE) {
  
          if ((ngx_uint_t) dx > ctx->max_width) {
              dy = dy * ctx->max_width / dx;
              dy = dy ? dy : 1;
              dx = ctx->max_width;
          }
  
          if ((ngx_uint_t) dy > ctx->max_height) {
              dx = dx * ctx->max_height / dy;
              dx = dx ? dx : 1;
              dy = ctx->max_height;
          }
  
          resize = 1;
  
      } else if (conf->filter == NGX_HTTP_IMAGE_ROTATE) {
  
          resize = 0;
  
      } else if (conf->filter == NGX_HTTP_IMAGE_WATERMARK) {
          resize = 0;
      } else { /* NGX_HTTP_IMAGE_CROP */
  
          resize = 0;
  
          if ((double) dx / dy < (double) ctx->max_width / ctx->max_height) {
              if ((ngx_uint_t) dx > ctx->max_width) {
                  dy = dy * ctx->max_width / dx;
                  dy = dy ? dy : 1;
                  dx = ctx->max_width;
                  resize = 1;
              }
  
          } else {
              if ((ngx_uint_t) dy > ctx->max_height) {
                  dx = dx * ctx->max_height / dy;
                  dx = dx ? dx : 1;
                  dy = ctx->max_height;
                  resize = 1;
              }
          }
      }
  
      if (resize) {
          dst = ngx_http_image_new(r, dx, dy, palette);
          if (dst == NULL) {
              gdImageDestroy(src);
              return NULL;
          }
  
          if (colors == 0) {
              gdImageSaveAlpha(dst, 1);
              gdImageAlphaBlending(dst, 0);
          }
  
          gdImageCopyResampled(dst, src, 0, 0, 0, 0, dx, dy, sx, sy);
  
          if (colors) {
              gdImageTrueColorToPalette(dst, 1, 256);
          }
  
          gdImageDestroy(src);
  
      } else {
          dst = src;
      }
  
      if (ctx->angle) {
          src = dst;
  
          ax = (dx % 2 == 0) ? 1 : 0;
          ay = (dy % 2 == 0) ? 1 : 0;
  
          switch (ctx->angle) {
  
          case 90:
          case 270:
              dst = ngx_http_image_new(r, dy, dx, palette);
              if (dst == NULL) {
                  gdImageDestroy(src);
                  return NULL;
              }
              if (ctx->angle == 90) {
                  ox = dy / 2 + ay;
                  oy = dx / 2 - ax;
  
              } else {
                  ox = dy / 2 - ay;
                  oy = dx / 2 + ax;
              }
  
              gdImageCopyRotated(dst, src, ox, oy, 0, 0,
                                 dx + ax, dy + ay, ctx->angle);
              gdImageDestroy(src);
  
              t = dx;
              dx = dy;
              dy = t;
              break;
  
          case 180:
              dst = ngx_http_image_new(r, dx, dy, palette);
              if (dst == NULL) {
                  gdImageDestroy(src);
                  return NULL;
              }
              gdImageCopyRotated(dst, src, dx / 2 - ax, dy / 2 - ay, 0, 0,
                                 dx + ax, dy + ay, ctx->angle);
              gdImageDestroy(src);
              break;
          }
      }
  
      if (conf->filter == NGX_HTTP_IMAGE_CROP) {
  
          src = dst;
  
          if ((ngx_uint_t) dx > ctx->max_width) {
              ox = dx - ctx->max_width;
  
          } else {
              ox = 0;
          }
  
          if ((ngx_uint_t) dy > ctx->max_height) {
              oy = dy - ctx->max_height;
  
          } else {
              oy = 0;
          }
  
          if (ox || oy) {
  
              dst = ngx_http_image_new(r, dx - ox, dy - oy, colors);
  
              if (dst == NULL) {
                  gdImageDestroy(src);
                  return NULL;
              }
  
              ox /= 2;
              oy /= 2;
  
              ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                             "image crop: %d x %d @ %d x %d",
                             dx, dy, ox, oy);
  
              if (colors == 0) {
                  gdImageSaveAlpha(dst, 1);
                  gdImageAlphaBlending(dst, 0);
              }
  
              gdImageCopy(dst, src, 0, 0, ox, oy, dx - ox, dy - oy);
  
              if (colors) {
                  gdImageTrueColorToPalette(dst, 1, 256);
              }
  
              gdImageDestroy(src);
          }
      }
  
      if (transparent != -1 && colors) {
          gdImageColorTransparent(dst, gdImageColorExact(dst, red, green, blue));
      }
  
      if (conf->watermark.data) {
              int min_w, min_h;
  
              min_w=ctx->max_width;
              min_h=ctx->max_height;
  
              if (!ctx->max_width && !ctx->max_height){
                  min_w=sx;
                  min_h=sy;
              }
  
              if ( min_w >= conf->watermark_width_from &&
                    min_h >= conf->watermark_height_from){
  
                  FILE *watermark_file = fopen((const char *)conf->watermark.data, "r");
  
                  if (watermark_file) {
                      gdImagePtr watermark, watermark_mix;
                      ngx_int_t wdx = 0, wdy = 0;
                      int wdw = 0, wdh = 0;
  
                      watermark = gdImageCreateFromPng(watermark_file);
                      
                      if(watermark != NULL) {
                          wdw = (int)watermark->sx;
                          wdh = (int)watermark->sy;
  
                          if (wdh > wdw) {
                              if (conf->watermark_relative_height) {
                                  wdh = (int)dst->sy/100*conf->watermark_relative_height;
                                  wdw = (int)((float)wdh/(float)watermark->sy*(float)watermark->sx);
  
                                  if (conf->watermark_relative_width 
                                          && wdw > (int)dst->sx/100*conf->watermark_relative_width) {
                                      wdw = (int)dst->sx/100*conf->watermark_relative_width;
                                      wdh = (int)((float)wdw/(float)watermark->sx*(float)watermark->sy);
                                  }
                              }
                          }
                          else {
                              if (conf->watermark_relative_width) {
                                  wdw = (int)dst->sx/100*conf->watermark_relative_width;
                                  wdh = (int)((float)wdw/(float)watermark->sx*(float)watermark->sy);
  
                                  if (conf->watermark_relative_height 
                                          && wdh > (int)dst->sy/100*conf->watermark_relative_height) {
                                      wdh = (int)dst->sy/100*conf->watermark_relative_height;
                                      wdw = (int)((float)wdh/(float)watermark->sy*(float)watermark->sx);
                                  }
                              }
                          }
  
  
                          watermark_mix = gdImageCreateTrueColor(wdw, wdh);
                          if (ngx_strcmp(conf->watermark_position.data, "bottom-right") == 0) {
                              wdx = (int)dst->sx - wdw - 10;
                              wdy = (int)dst->sy - wdh - 10;
                          } else if (ngx_strcmp(conf->watermark_position.data, "top-left") == 0) {
                              wdx = wdy = 10;
                          } else if (ngx_strcmp(conf->watermark_position.data, "top-right") == 0) {
                              wdx = (int)dst->sx - wdw - 10;
                              wdy = 10;
                          } else if (ngx_strcmp(conf->watermark_position.data, "bottom-left") == 0) {
                              wdx = 10;
                              wdy = (int)dst->sy - wdh - 10;
                          }else if (ngx_strcmp(conf->watermark_position.data, "top-center") == 0) {
                              wdy = 10;
                              wdx = (int)dst->sx/2 - (int)wdw/2;
                          }else if (ngx_strcmp(conf->watermark_position.data, "bottom-center") == 0) {
                              wdx = (int)dst->sx/2 - (int)wdw/2;
                              wdy = (int)dst->sy - wdh - 10;
                          }else if (ngx_strcmp(conf->watermark_position.data, "left-center") == 0) {
                              wdx = 10;
                              wdy = (int)dst->sy/2 - (int)wdh/2;
                          }else if (ngx_strcmp(conf->watermark_position.data, "right-center") == 0) {
                              wdx = (int)dst->sx - wdw - 10;
                              wdy = (int)dst->sy/2 - (int)wdh/2;
                          }else if (ngx_strcmp(conf->watermark_position.data, "center-center") == 0) {
                              wdx = (int)dst->sx/2 - (int)wdw/2;
                              wdy = (int)dst->sy/2 - (int)wdh/2;
                          }else if (ngx_strcmp(conf->watermark_position.data, "center-random") == 0) {
                              ngx_int_t randomBit = rand() & 1;
                              if (randomBit) {
                                  wdx = ((int)dst->sx/2 - (int)wdw/2) - (int)((double)rand() / ((double)RAND_MAX + 1) * 15);
                                  wdy = ((int)dst->sy/2 - (int)wdh/2) + (int)((double)rand() / ((double)RAND_MAX + 1) * 15);
                              } else {
                                  wdx = ((int)dst->sx/2 - (int)wdw/2) + (int)((double)rand() / ((double)RAND_MAX + 1) * 15);
                                  wdy = ((int)dst->sy/2 - (int)wdh/2) - (int)((double)rand() / ((double)RAND_MAX + 1) * 15);
                              }
                          }
  
                          gdImageCopy(watermark_mix, dst, 0, 0, wdx, wdy, wdw, wdh);
                          gdImageCopyResampled(watermark_mix, watermark, 0, 0, 0, 0, wdw, wdh, watermark->sx, watermark->sy);
                          gdImageCopyMerge(dst, watermark_mix, wdx, wdy, 0, 0, wdw, wdh, 75);
                          gdImageDestroy(watermark_mix);
  
                      } else {
                          ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "watermark file '%s' is not PNG", conf->watermark.data);
                      }
  
                      gdImageDestroy(watermark);
                  } else {
                      ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "watermark file '%s' not found", conf->watermark.data);
                  }
  
                  fclose(watermark_file);
              }else{
                  if (conf->filter == NGX_HTTP_IMAGE_WATERMARK)
                  {
                      gdImageDestroy(src);
                      return ngx_http_image_asis(r, ctx);
                  }
              }
          }
  
  
      sharpen = ngx_http_image_filter_get_value(r, conf->shcv, conf->sharpen);
      if (sharpen > 0) {
          gdImageSharpen(dst, sharpen);
      }
  
      gdImageInterlace(dst, (int) conf->interlace);
  
      out = ngx_http_image_out(r, ctx->type, dst, &size);
  
      ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                     "image: %d x %d %d", sx, sy, colors);
  
      gdImageDestroy(dst);
      ngx_pfree(r->pool, ctx->image);
  
      if (out == NULL) {
          return NULL;
      }
  
      cln = ngx_pool_cleanup_add(r->pool, 0);
      if (cln == NULL) {
          gdFree(out);
          return NULL;
      }
  
      b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
      if (b == NULL) {
          gdFree(out);
          return NULL;
      }
  
      cln->handler = ngx_http_image_cleanup;
      cln->data = out;
  
      b->pos = out;
      b->last = out + size;
      b->memory = 1;
      b->last_buf = 1;
  
      ngx_http_image_length(r, b);
      ngx_http_weak_etag(r);
  
      return b;
  }
  
  
  static gdImagePtr
  ngx_http_image_source(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
  {
      char        *failed;
      gdImagePtr   img;
  
      img = NULL;
  
      switch (ctx->type) {
  
      case NGX_HTTP_IMAGE_JPEG:
          img = gdImageCreateFromJpegPtr(ctx->length, ctx->image);
          failed = "gdImageCreateFromJpegPtr() failed";
          break;
  
      case NGX_HTTP_IMAGE_GIF:
          img = gdImageCreateFromGifPtr(ctx->length, ctx->image);
          failed = "gdImageCreateFromGifPtr() failed";
          break;
  
      case NGX_HTTP_IMAGE_PNG:
          img = gdImageCreateFromPngPtr(ctx->length, ctx->image);
          failed = "gdImageCreateFromPngPtr() failed";
          break;
  
      case NGX_HTTP_IMAGE_WEBP:
  #if (NGX_HAVE_GD_WEBP)
          img = gdImageCreateFromWebpPtr(ctx->length, ctx->image);
          failed = "gdImageCreateFromWebpPtr() failed";
  #else
          failed = "nginx was built without GD WebP support";
  #endif
          break;
  
      default:
          failed = "unknown image type";
          break;
      }
  
      if (img == NULL) {
          ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, failed);
      }
  
      return img;
  }
  
  
  static gdImagePtr
  ngx_http_image_new(ngx_http_request_t *r, int w, int h, int colors)
  {
      gdImagePtr  img;
  
      if (colors == 0) {
          img = gdImageCreateTrueColor(w, h);
  
          if (img == NULL) {
              ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                            "gdImageCreateTrueColor() failed");
              return NULL;
          }
  
      } else {
          img = gdImageCreate(w, h);
  
          if (img == NULL) {
              ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                            "gdImageCreate() failed");
              return NULL;
          }
      }
  
      return img;
  }
  
  
  static u_char *
  ngx_http_image_out(ngx_http_request_t *r, ngx_uint_t type, gdImagePtr img,
      int *size)
  {
      char                          *failed;
      u_char                        *out;
      ngx_int_t                      q;
      ngx_http_image_filter_conf_t  *conf;
  
      out = NULL;
  
      switch (type) {
  
      case NGX_HTTP_IMAGE_JPEG:
          conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
  
          q = ngx_http_image_filter_get_value(r, conf->jqcv, conf->jpeg_quality);
          if (q <= 0) {
              return NULL;
          }
  
          out = gdImageJpegPtr(img, size, q);
          failed = "gdImageJpegPtr() failed";
          break;
  
      case NGX_HTTP_IMAGE_GIF:
          out = gdImageGifPtr(img, size);
          failed = "gdImageGifPtr() failed";
          break;
  
      case NGX_HTTP_IMAGE_PNG:
          out = gdImagePngPtr(img, size);
          failed = "gdImagePngPtr() failed";
          break;
  
      case NGX_HTTP_IMAGE_WEBP:
  #if (NGX_HAVE_GD_WEBP)
          conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
  
          q = ngx_http_image_filter_get_value(r, conf->wqcv, conf->webp_quality);
          if (q <= 0) {
              return NULL;
          }
  
          out = gdImageWebpPtrEx(img, size, q);
          failed = "gdImageWebpPtrEx() failed";
  #else
          failed = "nginx was built without GD WebP support";
  #endif
          break;
  
      default:
          failed = "unknown image type";
          break;
      }
  
      if (out == NULL) {
          ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, failed);
      }
  
      return out;
  }
  
  
  static void
  ngx_http_image_cleanup(void *data)
  {
      gdFree(data);
  }
  
  
  static ngx_uint_t
  ngx_http_image_filter_get_value(ngx_http_request_t *r,
      ngx_http_complex_value_t *cv, ngx_uint_t v)
  {
      ngx_str_t  val;
  
      if (cv == NULL) {
          return v;
      }
  
      if (ngx_http_complex_value(r, cv, &val) != NGX_OK) {
          return 0;
      }
  
      return ngx_http_image_filter_value(&val);
  }
  
  
  static ngx_uint_t
  ngx_http_image_filter_value(ngx_str_t *value)
  {
      ngx_int_t  n;
  
      if (value->len == 1 && value->data[0] == '-') {
          return (ngx_uint_t) -1;
      }
  
      n = ngx_atoi(value->data, value->len);
  
      if (n > 0) {
          return (ngx_uint_t) n;
      }
  
      return 0;
  }
  
  
  static void *
  ngx_http_image_filter_create_conf(ngx_conf_t *cf)
  {
      ngx_http_image_filter_conf_t  *conf;
  
      conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_image_filter_conf_t));
      if (conf == NULL) {
          return NULL;
      }
  
      /*
       * set by ngx_pcalloc():
       *
       *     conf->width = 0;
       *     conf->height = 0;
       *     conf->angle = 0;
       *     conf->wcv = NULL;
       *     conf->hcv = NULL;
       *     conf->acv = NULL;
       *     conf->jqcv = NULL;
       *     conf->wqcv = NULL;
       *     conf->shcv = NULL;
       */
  
      conf->filter = NGX_CONF_UNSET_UINT;
      conf->jpeg_quality = NGX_CONF_UNSET_UINT;
      conf->webp_quality = NGX_CONF_UNSET_UINT;
      conf->sharpen = NGX_CONF_UNSET_UINT;
      conf->transparency = NGX_CONF_UNSET;
      conf->interlace = NGX_CONF_UNSET;
      conf->buffer_size = NGX_CONF_UNSET_SIZE;
  
      conf->watermark_width_from = NGX_CONF_UNSET_UINT;
      conf->watermark_height_from = NGX_CONF_UNSET_UINT;
      conf->watermark_relative_height = NGX_CONF_UNSET_UINT;
      conf->watermark_relative_width = NGX_CONF_UNSET_UINT;
  
      return conf;
  }
  
  
  static char *
  ngx_http_image_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child)
  {
      ngx_http_image_filter_conf_t *prev = parent;
      ngx_http_image_filter_conf_t *conf = child;
  
      if (conf->filter == NGX_CONF_UNSET_UINT) {
  
          if (prev->filter == NGX_CONF_UNSET_UINT) {
              conf->filter = NGX_HTTP_IMAGE_OFF;
  
          } else {
              conf->filter = prev->filter;
              conf->width = prev->width;
              conf->height = prev->height;
              conf->angle = prev->angle;
              conf->wcv = prev->wcv;
              conf->hcv = prev->hcv;
              conf->acv = prev->acv;
          }
      }
  
      if (conf->jpeg_quality == NGX_CONF_UNSET_UINT) {
  
          /* 75 is libjpeg default quality */
          ngx_conf_merge_uint_value(conf->jpeg_quality, prev->jpeg_quality, 75);
  
          if (conf->jqcv == NULL) {
              conf->jqcv = prev->jqcv;
          }
      }
  
      if (conf->webp_quality == NGX_CONF_UNSET_UINT) {
  
          /* 80 is libwebp default quality */
          ngx_conf_merge_uint_value(conf->webp_quality, prev->webp_quality, 80);
  
          if (conf->wqcv == NULL) {
              conf->wqcv = prev->wqcv;
          }
      }
  
      if (conf->sharpen == NGX_CONF_UNSET_UINT) {
          ngx_conf_merge_uint_value(conf->sharpen, prev->sharpen, 0);
  
          if (conf->shcv == NULL) {
              conf->shcv = prev->shcv;
          }
      }
  
      ngx_conf_merge_value(conf->transparency, prev->transparency, 1);
  
      ngx_conf_merge_value(conf->interlace, prev->interlace, 0);
  
      ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size,
                                1 * 1024 * 1024);
  
      ngx_conf_merge_str_value(conf->watermark, prev->watermark, NULL);
      ngx_conf_merge_str_value(conf->watermark_position, prev->watermark_position, "bottom-right");
      
      ngx_conf_merge_value(conf->watermark_height_from, prev->watermark_height_from, 0);
      ngx_conf_merge_value(conf->watermark_width_from, prev->watermark_height_from, 0);
      ngx_conf_merge_value(conf->watermark_relative_height, prev->watermark_relative_height, 0);
      ngx_conf_merge_value(conf->watermark_relative_width, prev->watermark_relative_width, 0);
      ngx_conf_merge_value(conf->watermark_relative_width, prev->watermark_relative_width, 0);
  
      return NGX_CONF_OK;
  }
  
  
  static char *
  ngx_http_image_filter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
  {
      ngx_http_image_filter_conf_t *imcf = conf;
  
      ngx_str_t                         *value;
      ngx_int_t                          n;
      ngx_uint_t                         i;
      ngx_http_complex_value_t           cv;
      ngx_http_compile_complex_value_t   ccv;
  
      value = cf->args->elts;
  
      i = 1;
  
      if (cf->args->nelts == 2) {
          if (ngx_strcmp(value[i].data, "off") == 0) {
              imcf->filter = NGX_HTTP_IMAGE_OFF;
  
          } else if (ngx_strcmp(value[i].data, "test") == 0) {
              imcf->filter = NGX_HTTP_IMAGE_TEST;
  
          } else if (ngx_strcmp(value[i].data, "size") == 0) {
              imcf->filter = NGX_HTTP_IMAGE_SIZE;
  
          } else if (ngx_strcmp(value[i].data, "watermark") == 0) {
              imcf->filter = NGX_HTTP_IMAGE_WATERMARK;
          } else {
              goto failed;
          }
  
          return NGX_CONF_OK;
  
      } else if (cf->args->nelts == 3) {
  
          if (ngx_strcmp(value[i].data, "rotate") == 0) {
              if (imcf->filter != NGX_HTTP_IMAGE_RESIZE
                  && imcf->filter != NGX_HTTP_IMAGE_CROP)
              {
                  imcf->filter = NGX_HTTP_IMAGE_ROTATE;
              }
  
              ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
  
              ccv.cf = cf;
              ccv.value = &value[++i];
              ccv.complex_value = &cv;
  
              if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
                  return NGX_CONF_ERROR;
              }
  
              if (cv.lengths == NULL) {
                  n = ngx_http_image_filter_value(&value[i]);
  
                  if (n != 90 && n != 180 && n != 270) {
                      goto failed;
                  }
  
                  imcf->angle = (ngx_uint_t) n;
  
              } else {
                  imcf->acv = ngx_palloc(cf->pool,
                                         sizeof(ngx_http_complex_value_t));
                  if (imcf->acv == NULL) {
                      return NGX_CONF_ERROR;
                  }
  
                  *imcf->acv = cv;
              }
  
              return NGX_CONF_OK;
  
          } else {
              goto failed;
          }
      }
  
      if (ngx_strcmp(value[i].data, "resize") == 0) {
          imcf->filter = NGX_HTTP_IMAGE_RESIZE;
  
      } else if (ngx_strcmp(value[i].data, "crop") == 0) {
          imcf->filter = NGX_HTTP_IMAGE_CROP;
      } else if (ngx_strcmp(value[i].data, "watermark") == 0) {
          imcf->filter = NGX_HTTP_IMAGE_WATERMARK;
      } else {
          goto failed;
      }
  
      ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
  
      ccv.cf = cf;
      ccv.value = &value[++i];
      ccv.complex_value = &cv;
  
      if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
          return NGX_CONF_ERROR;
      }
  
      if (cv.lengths == NULL) {
          n = ngx_http_image_filter_value(&value[i]);
  
          if (n == 0) {
              goto failed;
          }
  
          imcf->width = (ngx_uint_t) n;
  
      } else {
          imcf->wcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
          if (imcf->wcv == NULL) {
              return NGX_CONF_ERROR;
          }
  
          *imcf->wcv = cv;
      }
  
      ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
  
      ccv.cf = cf;
      ccv.value = &value[++i];
      ccv.complex_value = &cv;
  
      if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
          return NGX_CONF_ERROR;
      }
  
      if (cv.lengths == NULL) {
          n = ngx_http_image_filter_value(&value[i]);
  
          if (n == 0) {
              goto failed;
          }
  
          imcf->height = (ngx_uint_t) n;
  
      } else {
          imcf->hcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
          if (imcf->hcv == NULL) {
              return NGX_CONF_ERROR;
          }
  
          *imcf->hcv = cv;
      }
  
      return NGX_CONF_OK;
  
  failed:
  
      ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"",
                         &value[i]);
  
      return NGX_CONF_ERROR;
  }
  
  
  static char *
  ngx_http_image_filter_jpeg_quality(ngx_conf_t *cf, ngx_command_t *cmd,
      void *conf)
  {
      ngx_http_image_filter_conf_t *imcf = conf;
  
      ngx_str_t                         *value;
      ngx_int_t                          n;
      ngx_http_complex_value_t           cv;
      ngx_http_compile_complex_value_t   ccv;
  
      value = cf->args->elts;
  
      ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
  
      ccv.cf = cf;
      ccv.value = &value[1];
      ccv.complex_value = &cv;
  
      if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
          return NGX_CONF_ERROR;
      }
  
      if (cv.lengths == NULL) {
          n = ngx_http_image_filter_value(&value[1]);
  
          if (n <= 0) {
              ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                 "invalid value \"%V\"", &value[1]);
              return NGX_CONF_ERROR;
          }
  
          imcf->jpeg_quality = (ngx_uint_t) n;
  
      } else {
          imcf->jqcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
          if (imcf->jqcv == NULL) {
              return NGX_CONF_ERROR;
          }
  
          *imcf->jqcv = cv;
      }
  
      return NGX_CONF_OK;
  }
  
  
  static char *
  ngx_http_image_filter_webp_quality(ngx_conf_t *cf, ngx_command_t *cmd,
      void *conf)
  {
      ngx_http_image_filter_conf_t *imcf = conf;
  
      ngx_str_t                         *value;
      ngx_int_t                          n;
      ngx_http_complex_value_t           cv;
      ngx_http_compile_complex_value_t   ccv;
  
      value = cf->args->elts;
  
      ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
  
      ccv.cf = cf;
      ccv.value = &value[1];
      ccv.complex_value = &cv;
  
      if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
          return NGX_CONF_ERROR;
      }
  
      if (cv.lengths == NULL) {
          n = ngx_http_image_filter_value(&value[1]);
  
          if (n <= 0) {
              ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                 "invalid value \"%V\"", &value[1]);
              return NGX_CONF_ERROR;
          }
  
          imcf->webp_quality = (ngx_uint_t) n;
  
      } else {
          imcf->wqcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
          if (imcf->wqcv == NULL) {
              return NGX_CONF_ERROR;
          }
  
          *imcf->wqcv = cv;
      }
  
      return NGX_CONF_OK;
  }
  
  
  static char *
  ngx_http_image_filter_sharpen(ngx_conf_t *cf, ngx_command_t *cmd,
      void *conf)
  {
      ngx_http_image_filter_conf_t *imcf = conf;
  
      ngx_str_t                         *value;
      ngx_int_t                          n;
      ngx_http_complex_value_t           cv;
      ngx_http_compile_complex_value_t   ccv;
  
      value = cf->args->elts;
  
      ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
  
      ccv.cf = cf;
      ccv.value = &value[1];
      ccv.complex_value = &cv;
  
      if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
          return NGX_CONF_ERROR;
      }
  
      if (cv.lengths == NULL) {
          n = ngx_http_image_filter_value(&value[1]);
  
          if (n < 0) {
              ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                 "invalid value \"%V\"", &value[1]);
              return NGX_CONF_ERROR;
          }
  
          imcf->sharpen = (ngx_uint_t) n;
  
      } else {
          imcf->shcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
          if (imcf->shcv == NULL) {
              return NGX_CONF_ERROR;
          }
  
          *imcf->shcv = cv;
      }
  
      return NGX_CONF_OK;
  }
  
  
  static ngx_int_t
  ngx_http_image_filter_init(ngx_conf_t *cf)
  {
      ngx_http_next_header_filter = ngx_http_top_header_filter;
      ngx_http_top_header_filter = ngx_http_image_header_filter;
  
      ngx_http_next_body_filter = ngx_http_top_body_filter;
      ngx_http_top_body_filter = ngx_http_image_body_filter;
  
      return NGX_OK;
  }
  