image.c revision ceb6d34906c7c03c102c7e78dd02f5b0ebab4ca9
1/**************************************************************************
2 *
3 * Copyright 2009 VMware, Inc.  All Rights Reserved.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the
7 * "Software"), to deal in the Software without restriction, including
8 * without limitation the rights to use, copy, modify, merge, publish,
9 * distribute, sub license, and/or sell copies of the Software, and to
10 * permit persons to whom the Software is furnished to do so, subject to
11 * the following conditions:
12 *
13 * The above copyright notice and this permission notice (including the
14 * next paragraph) shall be included in all copies or substantial portions
15 * of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
20 * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR
21 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
22 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 *
25 **************************************************************************/
26
27#include "image.h"
28
29#include "vg_translate.h"
30#include "vg_context.h"
31#include "matrix.h"
32#include "renderer.h"
33#include "util_array.h"
34#include "api_consts.h"
35#include "shader.h"
36
37#include "pipe/p_context.h"
38#include "pipe/p_screen.h"
39#include "util/u_inlines.h"
40#include "util/u_format.h"
41#include "util/u_tile.h"
42#include "util/u_memory.h"
43#include "util/u_math.h"
44#include "util/u_sampler.h"
45#include "util/u_surface.h"
46
47static enum pipe_format vg_format_to_pipe(VGImageFormat format)
48{
49   switch(format) {
50   case VG_sRGB_565:
51      return PIPE_FORMAT_B5G6R5_UNORM;
52   case VG_sRGBA_5551:
53      return PIPE_FORMAT_B5G5R5A1_UNORM;
54   case VG_sRGBA_4444:
55      return PIPE_FORMAT_B4G4R4A4_UNORM;
56   case VG_sL_8:
57   case VG_lL_8:
58      return PIPE_FORMAT_L8_UNORM;
59   case VG_BW_1:
60      return PIPE_FORMAT_B8G8R8A8_UNORM;
61   case VG_A_8:
62      return PIPE_FORMAT_A8_UNORM;
63#ifdef OPENVG_VERSION_1_1
64   case VG_A_1:
65   case VG_A_4:
66      return PIPE_FORMAT_A8_UNORM;
67#endif
68   default:
69      return PIPE_FORMAT_B8G8R8A8_UNORM;
70   }
71}
72
73static INLINE void vg_sync_size(VGfloat *src_loc, VGfloat *dst_loc)
74{
75   src_loc[2] = MIN2(src_loc[2], dst_loc[2]);
76   src_loc[3] = MIN2(src_loc[3], dst_loc[3]);
77   dst_loc[2] = src_loc[2];
78   dst_loc[3] = src_loc[3];
79}
80
81static void vg_get_copy_coords(VGfloat *src_loc,
82                               VGfloat src_width, VGfloat src_height,
83                               VGfloat *dst_loc,
84                               VGfloat dst_width, VGfloat dst_height)
85{
86   VGfloat dst_bounds[4], src_bounds[4];
87   VGfloat src_shift[4], dst_shift[4], shift[4];
88
89   dst_bounds[0] = 0.f;
90   dst_bounds[1] = 0.f;
91   dst_bounds[2] = dst_width;
92   dst_bounds[3] = dst_height;
93
94   src_bounds[0] = 0.f;
95   src_bounds[1] = 0.f;
96   src_bounds[2] = src_width;
97   src_bounds[3] = src_height;
98
99   vg_bound_rect(src_loc, src_bounds, src_shift);
100   vg_bound_rect(dst_loc, dst_bounds, dst_shift);
101   shift[0] = src_shift[0] - dst_shift[0];
102   shift[1] = src_shift[1] - dst_shift[1];
103
104   if (shift[0] < 0)
105      vg_shift_rectx(src_loc, src_bounds, -shift[0]);
106   else
107      vg_shift_rectx(dst_loc, dst_bounds, shift[0]);
108
109   if (shift[1] < 0)
110      vg_shift_recty(src_loc, src_bounds, -shift[1]);
111   else
112      vg_shift_recty(dst_loc, dst_bounds, shift[1]);
113
114   vg_sync_size(src_loc, dst_loc);
115}
116
117static void vg_copy_texture(struct vg_context *ctx,
118                            struct pipe_resource *dst, VGint dx, VGint dy,
119                            struct pipe_sampler_view *src, VGint sx, VGint sy,
120                            VGint width, VGint height)
121{
122   VGfloat dst_loc[4], src_loc[4];
123
124   dst_loc[0] = dx;
125   dst_loc[1] = dy;
126   dst_loc[2] = width;
127   dst_loc[3] = height;
128
129   src_loc[0] = sx;
130   src_loc[1] = sy;
131   src_loc[2] = width;
132   src_loc[3] = height;
133
134   vg_get_copy_coords(src_loc, src->texture->width0, src->texture->height0,
135                      dst_loc, dst->width0, dst->height0);
136
137   if (src_loc[2] >= 0 && src_loc[3] >= 0 &&
138       dst_loc[2] >= 0 && dst_loc[3] >= 0) {
139      struct pipe_surface *surf, surf_tmpl;
140
141      /* get the destination surface */
142      u_surface_default_template(&surf_tmpl, dst, PIPE_BIND_RENDER_TARGET);
143      surf = ctx->pipe->create_surface(ctx->pipe, dst, &surf_tmpl);
144      if (surf && renderer_copy_begin(ctx->renderer, surf, VG_TRUE, src)) {
145         renderer_copy(ctx->renderer,
146               dst_loc[0], dst_loc[1], dst_loc[2], dst_loc[3],
147               src_loc[0], src_loc[1], src_loc[2], src_loc[3]);
148         renderer_copy_end(ctx->renderer);
149      }
150
151      pipe_surface_reference(&surf, NULL);
152   }
153}
154
155void vg_copy_surface(struct vg_context *ctx,
156                     struct pipe_surface *dst, VGint dx, VGint dy,
157                     struct pipe_surface *src, VGint sx, VGint sy,
158                     VGint width, VGint height)
159{
160   VGfloat dst_loc[4], src_loc[4];
161
162   dst_loc[0] = dx;
163   dst_loc[1] = dy;
164   dst_loc[2] = width;
165   dst_loc[3] = height;
166
167   src_loc[0] = sx;
168   src_loc[1] = sy;
169   src_loc[2] = width;
170   src_loc[3] = height;
171
172   vg_get_copy_coords(src_loc, src->width, src->height,
173                      dst_loc, dst->width, dst->height);
174
175   if (src_loc[2] > 0 && src_loc[3] > 0 &&
176       dst_loc[2] > 0 && dst_loc[3] > 0) {
177      if (src == dst)
178         renderer_copy_surface(ctx->renderer,
179                               src,
180                               src_loc[0],
181                               src->height - (src_loc[1] + src_loc[3]),
182                               src_loc[0] + src_loc[2],
183                               src->height - src_loc[1],
184                               dst,
185                               dst_loc[0],
186                               dst->height - (dst_loc[1] + dst_loc[3]),
187                               dst_loc[0] + dst_loc[2],
188                               dst->height - dst_loc[1],
189                               0, 0);
190      else
191         renderer_copy_surface(ctx->renderer,
192                               src,
193                               src_loc[0],
194                               src->height - src_loc[1],
195                               src_loc[0] + src_loc[2],
196                               src->height - (src_loc[1] + src_loc[3]),
197                               dst,
198                               dst_loc[0],
199                               dst->height - (dst_loc[1] + dst_loc[3]),
200                               dst_loc[0] + dst_loc[2],
201                               dst->height - dst_loc[1],
202                               0, 0);
203   }
204
205}
206
207static struct pipe_resource *image_texture(struct vg_image *img)
208{
209   struct pipe_resource *tex = img->sampler_view->texture;
210   return tex;
211}
212
213
214static void image_cleari(struct vg_image *img, VGint clear_colori,
215                         VGint x, VGint y, VGint width, VGint height)
216{
217   VGint *clearbuf;
218   VGint i;
219   VGfloat dwidth, dheight;
220
221   clearbuf = malloc(sizeof(VGint)*width*height);
222   for (i = 0; i < width*height; ++i)
223      clearbuf[i] = clear_colori;
224
225   dwidth = MIN2(width, img->width);
226   dheight = MIN2(height, img->height);
227
228   image_sub_data(img, clearbuf, width * sizeof(VGint),
229                  VG_sRGBA_8888,
230                  x, y, dwidth, dheight);
231   free(clearbuf);
232}
233
234struct vg_image * image_create(VGImageFormat format,
235                               VGint width, VGint height)
236{
237   struct vg_context *ctx = vg_current_context();
238   struct pipe_context *pipe = ctx->pipe;
239   struct vg_image *image = CALLOC_STRUCT(vg_image);
240   enum pipe_format pformat = vg_format_to_pipe(format);
241   struct pipe_resource pt, *newtex;
242   struct pipe_sampler_view view_templ;
243   struct pipe_sampler_view *view;
244   struct pipe_screen *screen = ctx->pipe->screen;
245
246   vg_init_object(&image->base, ctx, VG_OBJECT_IMAGE);
247
248   image->format = format;
249   image->width = width;
250   image->height = height;
251
252   image->sampler.wrap_s = PIPE_TEX_WRAP_CLAMP_TO_EDGE;
253   image->sampler.wrap_t = PIPE_TEX_WRAP_CLAMP_TO_EDGE;
254   image->sampler.wrap_r = PIPE_TEX_WRAP_CLAMP_TO_EDGE;
255   image->sampler.min_img_filter = PIPE_TEX_MIPFILTER_NEAREST;
256   image->sampler.mag_img_filter = PIPE_TEX_MIPFILTER_NEAREST;
257   image->sampler.normalized_coords = 1;
258
259   assert(screen->is_format_supported(screen, pformat, PIPE_TEXTURE_2D,
260                                      0, PIPE_BIND_SAMPLER_VIEW));
261
262   memset(&pt, 0, sizeof(pt));
263   pt.target = PIPE_TEXTURE_2D;
264   pt.format = pformat;
265   pt.last_level = 0;
266   pt.width0 = width;
267   pt.height0 = height;
268   pt.depth0 = 1;
269   pt.array_size = 1;
270   pt.bind = PIPE_BIND_SAMPLER_VIEW;
271
272   newtex = screen->resource_create(screen, &pt);
273
274   debug_assert(newtex);
275
276   u_sampler_view_default_template(&view_templ, newtex, newtex->format);
277   /* R, G, and B are treated as 1.0 for alpha-only formats in OpenVG */
278   if (newtex->format == PIPE_FORMAT_A8_UNORM) {
279      view_templ.swizzle_r = PIPE_SWIZZLE_ONE;
280      view_templ.swizzle_g = PIPE_SWIZZLE_ONE;
281      view_templ.swizzle_b = PIPE_SWIZZLE_ONE;
282   }
283
284   view = pipe->create_sampler_view(pipe, newtex, &view_templ);
285   /* want the texture to go away if the view is freed */
286   pipe_resource_reference(&newtex, NULL);
287
288   image->sampler_view = view;
289
290   vg_context_add_object(ctx, &image->base);
291
292   image_cleari(image, 0, 0, 0, image->width, image->height);
293   return image;
294}
295
296void image_destroy(struct vg_image *img)
297{
298   struct vg_context *ctx = vg_current_context();
299   vg_context_remove_object(ctx, &img->base);
300
301
302   if (img->parent) {
303      /* remove img from the parent child array */
304      int idx;
305      struct vg_image **array =
306         (struct vg_image **)img->parent->children_array->data;
307
308      for (idx = 0; idx < img->parent->children_array->num_elements; ++idx) {
309         struct vg_image *child = array[idx];
310         if (child == img) {
311            break;
312         }
313      }
314      debug_assert(idx < img->parent->children_array->num_elements);
315      array_remove_element(img->parent->children_array, idx);
316   }
317
318   if (img->children_array && img->children_array->num_elements) {
319      /* reparent the children */
320      VGint i;
321      struct vg_image *parent = img->parent;
322      struct vg_image **children =
323         (struct vg_image **)img->children_array->data;
324      if (!parent) {
325         VGint min_x = children[0]->x;
326         parent = children[0];
327
328         for (i = 1; i < img->children_array->num_elements; ++i) {
329            struct vg_image *child = children[i];
330            if (child->x < min_x) {
331               parent = child;
332            }
333         }
334      }
335
336      for (i = 0; i < img->children_array->num_elements; ++i) {
337         struct vg_image *child = children[i];
338         if (child != parent) {
339            child->parent = parent;
340            if (!parent->children_array) {
341               parent->children_array = array_create(
342                  sizeof(struct vg_image*));
343            }
344            array_append_data(parent->children_array,
345                              &child, 1);
346         } else
347            child->parent = NULL;
348      }
349      array_destroy(img->children_array);
350   }
351
352   vg_free_object(&img->base);
353
354   pipe_sampler_view_reference(&img->sampler_view, NULL);
355   FREE(img);
356}
357
358void image_clear(struct vg_image *img,
359                 VGint x, VGint y, VGint width, VGint height)
360{
361   struct vg_context *ctx = vg_current_context();
362   VGfloat *clear_colorf = ctx->state.vg.clear_color;
363   VGubyte r, g, b ,a;
364   VGint clear_colori;
365   /* FIXME: this is very nasty */
366   r = float_to_ubyte(clear_colorf[0]);
367   g = float_to_ubyte(clear_colorf[1]);
368   b = float_to_ubyte(clear_colorf[2]);
369   a = float_to_ubyte(clear_colorf[3]);
370   clear_colori = r << 24 | g << 16 | b << 8 | a;
371   image_cleari(img, clear_colori, x, y, width, height);
372}
373
374void image_sub_data(struct vg_image *image,
375                    const void * data,
376                    VGint dataStride,
377                    VGImageFormat dataFormat,
378                    VGint x, VGint y,
379                    VGint width, VGint height)
380{
381   const VGint yStep = 1;
382   VGubyte *src = (VGubyte *)data;
383   VGfloat temp[VEGA_MAX_IMAGE_WIDTH][4];
384   VGfloat *df = (VGfloat*)temp;
385   VGint i;
386   struct vg_context *ctx = vg_current_context();
387   struct pipe_context *pipe = ctx->pipe;
388   struct pipe_resource *texture = image_texture(image);
389   VGint xoffset = 0, yoffset = 0;
390
391   if (x < 0) {
392      xoffset -= x;
393      width += x;
394      x = 0;
395   }
396   if (y < 0) {
397      yoffset -= y;
398      height += y;
399      y = 0;
400   }
401
402   if (width <= 0 || height <= 0) {
403      vg_set_error(ctx, VG_ILLEGAL_ARGUMENT_ERROR);
404      return;
405   }
406
407   if (x > image->width || y > image->width) {
408      vg_set_error(ctx, VG_ILLEGAL_ARGUMENT_ERROR);
409      return;
410   }
411
412   if (x + width > image->width) {
413      width = image->width - x;
414   }
415
416   if (y + height > image->height) {
417      height = image->height - y;
418   }
419
420   { /* upload color_data */
421      struct pipe_transfer *transfer = pipe_get_transfer(
422         pipe, texture, 0, 0,
423         PIPE_TRANSFER_WRITE, 0, 0, texture->width0, texture->height0);
424      src += (dataStride * yoffset);
425      for (i = 0; i < height; i++) {
426         _vega_unpack_float_span_rgba(ctx, width, xoffset, src, dataFormat, temp);
427         pipe_put_tile_rgba(pipe, transfer, x+image->x, y+image->y, width, 1, df);
428         y += yStep;
429         src += dataStride;
430      }
431      pipe->transfer_destroy(pipe, transfer);
432   }
433}
434
435void image_get_sub_data(struct vg_image * image,
436                        void * data,
437                        VGint dataStride,
438                        VGImageFormat dataFormat,
439                        VGint sx, VGint sy,
440                        VGint width, VGint height)
441{
442   struct vg_context *ctx = vg_current_context();
443   struct pipe_context *pipe = ctx->pipe;
444   VGfloat temp[VEGA_MAX_IMAGE_WIDTH][4];
445   VGfloat *df = (VGfloat*)temp;
446   VGint y = 0, yStep = 1;
447   VGint i;
448   VGubyte *dst = (VGubyte *)data;
449
450   {
451      struct pipe_transfer *transfer =
452         pipe_get_transfer(pipe,
453                           image->sampler_view->texture,  0, 0,
454                           PIPE_TRANSFER_READ,
455                           0, 0,
456                           image->x + image->width,
457                           image->y + image->height);
458      /* Do a row at a time to flip image data vertically */
459      for (i = 0; i < height; i++) {
460#if 0
461         debug_printf("%d-%d  == %d\n", sy, height, y);
462#endif
463         pipe_get_tile_rgba(pipe, transfer, sx+image->x, y, width, 1, df);
464         y += yStep;
465         _vega_pack_rgba_span_float(ctx, width, temp, dataFormat, dst);
466         dst += dataStride;
467      }
468
469      pipe->transfer_destroy(pipe, transfer);
470   }
471}
472
473struct vg_image * image_child_image(struct vg_image *parent,
474                                    VGint x, VGint y,
475                                    VGint width, VGint height)
476{
477   struct vg_context *ctx = vg_current_context();
478   struct vg_image *image = CALLOC_STRUCT(vg_image);
479
480   vg_init_object(&image->base, ctx, VG_OBJECT_IMAGE);
481
482   image->x = parent->x + x;
483   image->y = parent->y + y;
484   image->width = width;
485   image->height = height;
486   image->parent = parent;
487   image->sampler_view = NULL;
488   pipe_sampler_view_reference(&image->sampler_view,
489                               parent->sampler_view);
490
491   image->sampler.wrap_s = PIPE_TEX_WRAP_CLAMP_TO_EDGE;
492   image->sampler.wrap_t = PIPE_TEX_WRAP_CLAMP_TO_EDGE;
493   image->sampler.wrap_r = PIPE_TEX_WRAP_CLAMP_TO_EDGE;
494   image->sampler.min_img_filter = PIPE_TEX_MIPFILTER_NEAREST;
495   image->sampler.mag_img_filter = PIPE_TEX_MIPFILTER_NEAREST;
496   image->sampler.normalized_coords = 1;
497
498   if (!parent->children_array)
499      parent->children_array = array_create(
500         sizeof(struct vg_image*));
501
502   array_append_data(parent->children_array,
503                     &image, 1);
504
505   vg_context_add_object(ctx, &image->base);
506
507   return image;
508}
509
510void image_copy(struct vg_image *dst, VGint dx, VGint dy,
511                struct vg_image *src, VGint sx, VGint sy,
512                VGint width, VGint height,
513                VGboolean dither)
514{
515   struct vg_context *ctx = vg_current_context();
516
517   if (width <= 0 || height <= 0) {
518      vg_set_error(ctx, VG_ILLEGAL_ARGUMENT_ERROR);
519      return;
520   }
521   vg_copy_texture(ctx, dst->sampler_view->texture, dst->x + dx, dst->y + dy,
522                   src->sampler_view, src->x + sx, src->y + sy, width, height);
523}
524
525void image_draw(struct vg_image *img, struct matrix *matrix)
526{
527   struct vg_context *ctx = vg_current_context();
528   struct matrix paint_matrix;
529   VGfloat x1, y1;
530   VGfloat x2, y2;
531   VGfloat x3, y3;
532   VGfloat x4, y4;
533
534   if (!vg_get_paint_matrix(ctx,
535                            &ctx->state.vg.fill_paint_to_user_matrix,
536                            matrix,
537                            &paint_matrix))
538      return;
539
540   x1 = 0;
541   y1 = 0;
542   x2 = img->width;
543   y2 = 0;
544   x3 = img->width;
545   y3 = img->height;
546   x4 = 0;
547   y4 = img->height;
548
549   shader_set_surface_matrix(ctx->shader, matrix);
550   shader_set_drawing_image(ctx->shader, VG_TRUE);
551   shader_set_paint(ctx->shader, ctx->state.vg.fill_paint);
552   shader_set_paint_matrix(ctx->shader, &paint_matrix);
553   shader_set_image(ctx->shader, img);
554   shader_bind(ctx->shader);
555
556   renderer_texture_quad(ctx->renderer, image_texture(img),
557                         img->x, img->y, img->x + img->width, img->y + img->height,
558                         x1, y1, x2, y2, x3, y3, x4, y4);
559}
560
561void image_set_pixels(VGint dx, VGint dy,
562                      struct vg_image *src, VGint sx, VGint sy,
563                      VGint width, VGint height)
564{
565   struct vg_context *ctx = vg_current_context();
566   struct pipe_context *pipe = ctx->pipe;
567   struct pipe_surface *surf, surf_tmpl;
568   struct st_renderbuffer *strb = ctx->draw_buffer->strb;
569
570   memset(&surf_tmpl, 0, sizeof(surf_tmpl));
571   u_surface_default_template(&surf_tmpl, image_texture(src),
572                              0 /* no bind flag - not a surface*/);
573   surf = pipe->create_surface(pipe, image_texture(src), &surf_tmpl);
574
575   vg_copy_surface(ctx, strb->surface, dx, dy,
576                   surf, sx+src->x, sy+src->y, width, height);
577
578   pipe->surface_destroy(pipe, surf);
579}
580
581void image_get_pixels(struct vg_image *dst, VGint dx, VGint dy,
582                      VGint sx, VGint sy,
583                      VGint width, VGint height)
584{
585   struct vg_context *ctx = vg_current_context();
586   struct pipe_context *pipe = ctx->pipe;
587   struct pipe_surface *surf, surf_tmpl;
588   struct st_renderbuffer *strb = ctx->draw_buffer->strb;
589
590   /* flip the y coordinates */
591   /*dy = dst->height - dy - height;*/
592
593   memset(&surf_tmpl, 0, sizeof(surf_tmpl));
594   u_surface_default_template(&surf_tmpl, image_texture(dst),
595                              PIPE_BIND_RENDER_TARGET);
596   surf = pipe->create_surface(pipe, image_texture(dst), &surf_tmpl);
597
598   vg_copy_surface(ctx, surf, dst->x + dx, dst->y + dy,
599                   strb->surface, sx, sy, width, height);
600
601   pipe_surface_reference(&surf, NULL);
602}
603
604
605VGboolean vg_image_overlaps(struct vg_image *dst,
606                            struct vg_image *src)
607{
608   if (dst == src || dst->parent == src ||
609       dst == src->parent)
610      return VG_TRUE;
611   if (dst->parent && dst->parent == src->parent) {
612      VGfloat left1 = dst->x;
613      VGfloat left2 = src->x;
614      VGfloat right1 = dst->x + dst->width;
615      VGfloat right2 = src->x + src->width;
616      VGfloat bottom1 = dst->y;
617      VGfloat bottom2 = src->y;
618      VGfloat top1 = dst->y + dst->height;
619      VGfloat top2 = src->y + src->height;
620
621      return !(left2 > right1 || right2 < left1 ||
622               top2 > bottom1 || bottom2 < top1);
623   }
624   return VG_FALSE;
625}
626
627VGint image_bind_samplers(struct vg_image *img, struct pipe_sampler_state **samplers,
628                          struct pipe_sampler_view **sampler_views)
629{
630   img->sampler.min_img_filter = image_sampler_filter(img->base.ctx);
631   img->sampler.mag_img_filter = image_sampler_filter(img->base.ctx);
632   samplers[3] = &img->sampler;
633   sampler_views[3] = img->sampler_view;
634   return 1;
635}
636
637VGint image_sampler_filter(struct vg_context *ctx)
638{
639    switch(ctx->state.vg.image_quality) {
640    case VG_IMAGE_QUALITY_NONANTIALIASED:
641       return PIPE_TEX_FILTER_NEAREST;
642       break;
643    case VG_IMAGE_QUALITY_FASTER:
644       return PIPE_TEX_FILTER_NEAREST;
645       break;
646    case VG_IMAGE_QUALITY_BETTER:
647       /* possibly use anisotropic filtering */
648       return PIPE_TEX_FILTER_LINEAR;
649       break;
650    default:
651       debug_printf("Unknown image quality");
652    }
653    return PIPE_TEX_FILTER_NEAREST;
654}
655