/* SPDX-License-Identifier: GPL-2.0-or-later * Copyright 2011 Blender Foundation. */ #include #include "COM_DoubleEdgeMaskOperation.h" namespace blender::compositor { /* This part has been copied from the double edge mask. */ static void do_adjacentKeepBorders( uint t, uint rw, const uint *limask, const uint *lomask, uint *lres, float *res, uint *rsize) { int x; uint isz = 0; /* Inner edge size. */ uint osz = 0; /* Outer edge size. */ uint gsz = 0; /* Gradient fill area size. */ /* Test the four corners */ /* Upper left corner. */ x = t - rw + 1; /* Test if inner mask is filled. */ if (limask[x]) { /* Test if pixel underneath, or to the right, are empty in the inner mask, * but filled in the outer mask. */ if ((!limask[x - rw] && lomask[x - rw]) || (!limask[x + 1] && lomask[x + 1])) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } /* Upper right corner. */ x = t; /* Test if inner mask is filled. */ if (limask[x]) { /* Test if pixel underneath, or to the left, are empty in the inner mask, * but filled in the outer mask. */ if ((!limask[x - rw] && lomask[x - rw]) || (!limask[x - 1] && lomask[x - 1])) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } /* Lower left corner. */ x = 0; /* Test if inner mask is filled. */ if (limask[x]) { /* Test if pixel above, or to the right, are empty in the inner mask, * but filled in the outer mask. */ if ((!limask[x + rw] && lomask[x + rw]) || (!limask[x + 1] && lomask[x + 1])) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } /* Lower right corner. */ x = rw - 1; /* Test if inner mask is filled. */ if (limask[x]) { /* Test if pixel above, or to the left, are empty in the inner mask, * but filled in the outer mask. */ if ((!limask[x + rw] && lomask[x + rw]) || (!limask[x - 1] && lomask[x - 1])) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } /* Test the TOP row of pixels in buffer, except corners */ for (x = t - 1; x >= (t - rw) + 2; x--) { /* Test if inner mask is filled. */ if (limask[x]) { /* Test if pixel to the right, or to the left, are empty in the inner mask, * but filled in the outer mask. */ if ((!limask[x - 1] && lomask[x - 1]) || (!limask[x + 1] && lomask[x + 1])) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } } /* Test the BOTTOM row of pixels in buffer, except corners */ for (x = rw - 2; x; x--) { /* Test if inner mask is filled. */ if (limask[x]) { /* Test if pixel to the right, or to the left, are empty in the inner mask, * but filled in the outer mask. */ if ((!limask[x - 1] && lomask[x - 1]) || (!limask[x + 1] && lomask[x + 1])) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } } /* Test the LEFT edge of pixels in buffer, except corners */ for (x = t - (rw << 1) + 1; x >= rw; x -= rw) { /* Test if inner mask is filled. */ if (limask[x]) { /* Test if pixel underneath, or above, are empty in the inner mask, * but filled in the outer mask. */ if ((!limask[x - rw] && lomask[x - rw]) || (!limask[x + rw] && lomask[x + rw])) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } } /* Test the RIGHT edge of pixels in buffer, except corners */ for (x = t - rw; x > rw; x -= rw) { /* Test if inner mask is filled. */ if (limask[x]) { /* Test if pixel underneath, or above, are empty in the inner mask, * but filled in the outer mask. */ if ((!limask[x - rw] && lomask[x - rw]) || (!limask[x + rw] && lomask[x + rw])) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } } rsize[0] = isz; /* Fill in our return sizes for edges + fill. */ rsize[1] = osz; rsize[2] = gsz; } static void do_adjacentBleedBorders( uint t, uint rw, const uint *limask, const uint *lomask, uint *lres, float *res, uint *rsize) { int x; uint isz = 0; /* Inner edge size. */ uint osz = 0; /* Outer edge size. */ uint gsz = 0; /* Gradient fill area size. */ /* Test the four corners */ /* Upper left corner. */ x = t - rw + 1; /* Test if inner mask is filled. */ if (limask[x]) { /* Test if pixel underneath, or to the right, are empty in the inner mask, * but filled in the outer mask. */ if ((!limask[x - rw] && lomask[x - rw]) || (!limask[x + 1] && lomask[x + 1])) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ if (!lomask[x - rw] || !lomask[x + 1]) { /* Test if outer mask is empty underneath or to the right. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } else { gsz++; /* Increment the gradient pixel count. */ lres[x] = 2; /* Flag pixel as gradient. */ } } /* Upper right corner. */ x = t; /* Test if inner mask is filled. */ if (limask[x]) { /* Test if pixel underneath, or to the left, are empty in the inner mask, * but filled in the outer mask. */ if ((!limask[x - rw] && lomask[x - rw]) || (!limask[x - 1] && lomask[x - 1])) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ if (!lomask[x - rw] || !lomask[x - 1]) { /* Test if outer mask is empty underneath or to the left. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } else { gsz++; /* Increment the gradient pixel count. */ lres[x] = 2; /* Flag pixel as gradient. */ } } /* Lower left corner. */ x = 0; /* Test if inner mask is filled. */ if (limask[x]) { /* Test if pixel above, or to the right, are empty in the inner mask, * But filled in the outer mask. */ if ((!limask[x + rw] && lomask[x + rw]) || (!limask[x + 1] && lomask[x + 1])) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ if (!lomask[x + rw] || !lomask[x + 1]) { /* Test if outer mask is empty above or to the right. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } else { gsz++; /* Increment the gradient pixel count. */ lres[x] = 2; /* Flag pixel as gradient. */ } } /* Lower right corner. */ x = rw - 1; /* Test if inner mask is filled. */ if (limask[x]) { /* Test if pixel above, or to the left, are empty in the inner mask, * but filled in the outer mask. */ if ((!limask[x + rw] && lomask[x + rw]) || (!limask[x - 1] && lomask[x - 1])) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ if (!lomask[x + rw] || !lomask[x - 1]) { /* Test if outer mask is empty above or to the left. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } else { gsz++; /* Increment the gradient pixel count. */ lres[x] = 2; /* Flag pixel as gradient. */ } } /* Test the TOP row of pixels in buffer, except corners */ for (x = t - 1; x >= (t - rw) + 2; x--) { /* Test if inner mask is filled. */ if (limask[x]) { /* Test if pixel to the left, or to the right, are empty in the inner mask, * but filled in the outer mask. */ if ((!limask[x - 1] && lomask[x - 1]) || (!limask[x + 1] && lomask[x + 1])) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ if (!lomask[x - 1] || !lomask[x + 1]) { /* Test if outer mask is empty to the left or to the right. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } else { gsz++; /* Increment the gradient pixel count. */ lres[x] = 2; /* Flag pixel as gradient. */ } } } /* Test the BOTTOM row of pixels in buffer, except corners */ for (x = rw - 2; x; x--) { /* Test if inner mask is filled. */ if (limask[x]) { /* Test if pixel to the left, or to the right, are empty in the inner mask, * but filled in the outer mask. */ if ((!limask[x - 1] && lomask[x - 1]) || (!limask[x + 1] && lomask[x + 1])) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ if (!lomask[x - 1] || !lomask[x + 1]) { /* Test if outer mask is empty to the left or to the right. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } else { gsz++; /* Increment the gradient pixel count. */ lres[x] = 2; /* Flag pixel as gradient. */ } } } /* Test the LEFT edge of pixels in buffer, except corners */ for (x = t - (rw << 1) + 1; x >= rw; x -= rw) { /* Test if inner mask is filled. */ if (limask[x]) { /* Test if pixel underneath, or above, are empty in the inner mask, * but filled in the outer mask. */ if ((!limask[x - rw] && lomask[x - rw]) || (!limask[x + rw] && lomask[x + rw])) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ if (!lomask[x - rw] || !lomask[x + rw]) { /* Test if outer mask is empty underneath or above. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } else { gsz++; /* Increment the gradient pixel count. */ lres[x] = 2; /* Flag pixel as gradient. */ } } } /* Test the RIGHT edge of pixels in buffer, except corners */ for (x = t - rw; x > rw; x -= rw) { /* Test if inner mask is filled. */ if (limask[x]) { /* Test if pixel underneath, or above, are empty in the inner mask, * But filled in the outer mask. */ if ((!limask[x - rw] && lomask[x - rw]) || (!limask[x + rw] && lomask[x + rw])) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ if (!lomask[x - rw] || !lomask[x + rw]) { /* Test if outer mask is empty underneath or above. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } else { gsz++; /* Increment the gradient pixel count. */ lres[x] = 2; /* Flag pixel as gradient. */ } } } rsize[0] = isz; /* Fill in our return sizes for edges + fill. */ rsize[1] = osz; rsize[2] = gsz; } static void do_allKeepBorders( uint t, uint rw, const uint *limask, const uint *lomask, uint *lres, float *res, uint *rsize) { int x; uint isz = 0; /* Inner edge size. */ uint osz = 0; /* Outer edge size. */ uint gsz = 0; /* Gradient fill area size. */ /* Test the four corners. */ /* Upper left corner. */ x = t - rw + 1; /* Test if inner mask is filled. */ if (limask[x]) { /* Test if the inner mask is empty underneath or to the right. */ if (!limask[x - rw] || !limask[x + 1]) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } /* Upper right corner. */ x = t; /* Test if inner mask is filled. */ if (limask[x]) { /* Test if the inner mask is empty underneath or to the left. */ if (!limask[x - rw] || !limask[x - 1]) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } /* Lower left corner. */ x = 0; /* Test if inner mask is filled. */ if (limask[x]) { /* Test if inner mask is empty above or to the right. */ if (!limask[x + rw] || !limask[x + 1]) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } /* Lower right corner. */ x = rw - 1; /* Test if inner mask is filled. */ if (limask[x]) { /* Test if inner mask is empty above or to the left. */ if (!limask[x + rw] || !limask[x - 1]) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } /* Test the TOP row of pixels in buffer, except corners */ for (x = t - 1; x >= (t - rw) + 2; x--) { /* Test if inner mask is filled. */ if (limask[x]) { /* Test if inner mask is empty to the left or to the right. */ if (!limask[x - 1] || !limask[x + 1]) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } } /* Test the BOTTOM row of pixels in buffer, except corners */ for (x = rw - 2; x; x--) { /* Test if inner mask is filled. */ if (limask[x]) { /* Test if inner mask is empty to the left or to the right. */ if (!limask[x - 1] || !limask[x + 1]) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } } /* Test the LEFT edge of pixels in buffer, except corners */ for (x = t - (rw << 1) + 1; x >= rw; x -= rw) { /* Test if inner mask is filled. */ if (limask[x]) { /* Test if inner mask is empty underneath or above. */ if (!limask[x - rw] || !limask[x + rw]) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } } /* Test the RIGHT edge of pixels in buffer, except corners */ for (x = t - rw; x > rw; x -= rw) { /* Test if inner mask is filled. */ if (limask[x]) { /* Test if inner mask is empty underneath or above. */ if (!limask[x - rw] || !limask[x + rw]) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } } rsize[0] = isz; /* Fill in our return sizes for edges + fill. */ rsize[1] = osz; rsize[2] = gsz; } static void do_allBleedBorders( uint t, uint rw, const uint *limask, const uint *lomask, uint *lres, float *res, uint *rsize) { int x; uint isz = 0; /* Inner edge size. */ uint osz = 0; /* Outer edge size. */ uint gsz = 0; /* Gradient fill area size. */ /* Test the four corners */ /* Upper left corner. */ x = t - rw + 1; /* Test if inner mask is filled. */ if (limask[x]) { /* Test if the inner mask is empty underneath or to the right. */ if (!limask[x - rw] || !limask[x + 1]) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ if (!lomask[x - rw] || !lomask[x + 1]) { /* Test if outer mask is empty underneath or to the right. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } else { gsz++; /* Increment the gradient pixel count. */ lres[x] = 2; /* Flag pixel as gradient. */ } } /* Upper right corner. */ x = t; /* Test if inner mask is filled. */ if (limask[x]) { /* Test if the inner mask is empty underneath or to the left. */ if (!limask[x - rw] || !limask[x - 1]) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ if (!lomask[x - rw] || !lomask[x - 1]) { /* Test if outer mask is empty above or to the left. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } else { gsz++; /* Increment the gradient pixel count. */ lres[x] = 2; /* Flag pixel as gradient. */ } } /* Lower left corner. */ x = 0; /* Test if inner mask is filled. */ if (limask[x]) { /* Test if inner mask is empty above or to the right. */ if (!limask[x + rw] || !limask[x + 1]) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ if (!lomask[x + rw] || !lomask[x + 1]) { /* Test if outer mask is empty underneath or to the right. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } else { gsz++; /* Increment the gradient pixel count. */ lres[x] = 2; /* Flag pixel as gradient. */ } } /* Lower right corner. */ x = rw - 1; /* Test if inner mask is filled. */ if (limask[x]) { /* Test if inner mask is empty above or to the left. */ if (!limask[x + rw] || !limask[x - 1]) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ if (!lomask[x + rw] || !lomask[x - 1]) { /* Test if outer mask is empty underneath or to the left. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } else { gsz++; /* Increment the gradient pixel count. */ lres[x] = 2; /* Flag pixel as gradient. */ } } /* Test the TOP row of pixels in buffer, except corners */ for (x = t - 1; x >= (t - rw) + 2; x--) { /* Test if inner mask is filled. */ if (limask[x]) { /* Test if inner mask is empty to the left or to the right. */ if (!limask[x - 1] || !limask[x + 1]) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ if (!lomask[x - 1] || !lomask[x + 1]) { /* Test if outer mask is empty to the left or to the right. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } else { gsz++; /* Increment the gradient pixel count. */ lres[x] = 2; /* Flag pixel as gradient. */ } } } /* Test the BOTTOM row of pixels in buffer, except corners */ for (x = rw - 2; x; x--) { /* Test if inner mask is filled. */ if (limask[x]) { /* Test if inner mask is empty to the left or to the right. */ if (!limask[x - 1] || !limask[x + 1]) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ if (!lomask[x - 1] || !lomask[x + 1]) { /* Test if outer mask is empty to the left or to the right. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } else { gsz++; /* Increment the gradient pixel count. */ lres[x] = 2; /* Flag pixel as gradient. */ } } } /* Test the LEFT edge of pixels in buffer, except corners */ for (x = t - (rw << 1) + 1; x >= rw; x -= rw) { /* Test if inner mask is filled. */ if (limask[x]) { /* Test if inner mask is empty underneath or above. */ if (!limask[x - rw] || !limask[x + rw]) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ if (!lomask[x - rw] || !lomask[x + rw]) { /* Test if outer mask is empty underneath or above. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } else { gsz++; /* Increment the gradient pixel count. */ lres[x] = 2; /* Flag pixel as gradient. */ } } } /* Test the RIGHT edge of pixels in buffer, except corners */ for (x = t - rw; x > rw; x -= rw) { /* Test if inner mask is filled. */ if (limask[x]) { /* Test if inner mask is empty underneath or above. */ if (!limask[x - rw] || !limask[x + rw]) { isz++; /* Increment inner edge size. */ lres[x] = 4; /* Flag pixel as inner edge. */ } else { res[x] = 1.0f; /* Pixel is just part of inner mask, and it's not an edge. */ } } else if (lomask[x]) { /* Inner mask was empty, test if outer mask is filled. */ if (!lomask[x - rw] || !lomask[x + rw]) { /* Test if outer mask is empty underneath or above. */ osz++; /* Increment outer edge size. */ lres[x] = 3; /* Flag pixel as outer edge. */ } else { gsz++; /* Increment the gradient pixel count. */ lres[x] = 2; /* Flag pixel as gradient. */ } } } rsize[0] = isz; /* Fill in our return sizes for edges + fill. */ rsize[1] = osz; rsize[2] = gsz; } static void do_allEdgeDetection(uint t, uint rw, const uint *limask, const uint *lomask, uint *lres, float *res, uint *rsize, uint in_isz, uint in_osz, uint in_gsz) { int x; /* Pixel loop counter. */ int a; /* Pixel loop counter. */ int dx; /* Delta x. */ int pix_prevRow; /* Pixel one row behind the one we are testing in a loop. */ int pix_nextRow; /* Pixel one row in front of the one we are testing in a loop. */ int pix_prevCol; /* Pixel one column behind the one we are testing in a loop. */ int pix_nextCol; /* Pixel one column in front of the one we are testing in a loop. */ /* Test all rows between the FIRST and LAST rows, excluding left and right edges */ for (x = (t - rw) + 1, dx = x - (rw - 2); dx > rw; x -= rw, dx -= rw) { a = x - 2; pix_prevRow = a + rw; pix_nextRow = a - rw; pix_prevCol = a + 1; pix_nextCol = a - 1; while (a > dx - 2) { if (!limask[a]) { /* If the inner mask is empty. */ if (lomask[a]) { /* If the outer mask is full. */ /* * Next we test all 4 directions around the current pixel: next/previous/up/down * The test ensures that the outer mask is empty and that the inner mask * is also empty. If both conditions are true for any one of the 4 adjacent pixels * then the current pixel is counted as being a true outer edge pixel. */ if ((!lomask[pix_nextCol] && !limask[pix_nextCol]) || (!lomask[pix_prevCol] && !limask[pix_prevCol]) || (!lomask[pix_nextRow] && !limask[pix_nextRow]) || (!lomask[pix_prevRow] && !limask[pix_prevRow])) { in_osz++; /* Increment the outer boundary pixel count. */ lres[a] = 3; /* Flag pixel as part of outer edge. */ } else { /* It's not a boundary pixel, but it is a gradient pixel. */ in_gsz++; /* Increment the gradient pixel count. */ lres[a] = 2; /* Flag pixel as gradient. */ } } } else { if (!limask[pix_nextCol] || !limask[pix_prevCol] || !limask[pix_nextRow] || !limask[pix_prevRow]) { in_isz++; /* Increment the inner boundary pixel count. */ lres[a] = 4; /* Flag pixel as part of inner edge. */ } else { res[a] = 1.0f; /* Pixel is part of inner mask, but not at an edge. */ } } a--; pix_prevRow--; pix_nextRow--; pix_prevCol--; pix_nextCol--; } } rsize[0] = in_isz; /* Fill in our return sizes for edges + fill. */ rsize[1] = in_osz; rsize[2] = in_gsz; } static void do_adjacentEdgeDetection(uint t, uint rw, const uint *limask, const uint *lomask, uint *lres, float *res, uint *rsize, uint in_isz, uint in_osz, uint in_gsz) { int x; /* Pixel loop counter. */ int a; /* Pixel loop counter. */ int dx; /* Delta x. */ int pix_prevRow; /* Pixel one row behind the one we are testing in a loop. */ int pix_nextRow; /* Pixel one row in front of the one we are testing in a loop. */ int pix_prevCol; /* Pixel one column behind the one we are testing in a loop. */ int pix_nextCol; /* Pixel one column in front of the one we are testing in a loop. */ /* Test all rows between the FIRST and LAST rows, excluding left and right edges */ for (x = (t - rw) + 1, dx = x - (rw - 2); dx > rw; x -= rw, dx -= rw) { a = x - 2; pix_prevRow = a + rw; pix_nextRow = a - rw; pix_prevCol = a + 1; pix_nextCol = a - 1; while (a > dx - 2) { if (!limask[a]) { /* If the inner mask is empty. */ if (lomask[a]) { /* If the outer mask is full. */ /* * Next we test all 4 directions around the current pixel: next/previous/up/down * The test ensures that the outer mask is empty and that the inner mask * is also empty. If both conditions are true for any one of the 4 adjacent pixels * then the current pixel is counted as being a true outer edge pixel. */ if ((!lomask[pix_nextCol] && !limask[pix_nextCol]) || (!lomask[pix_prevCol] && !limask[pix_prevCol]) || (!lomask[pix_nextRow] && !limask[pix_nextRow]) || (!lomask[pix_prevRow] && !limask[pix_prevRow])) { in_osz++; /* Increment the outer boundary pixel count. */ lres[a] = 3; /* Flag pixel as part of outer edge. */ } else { /* It's not a boundary pixel, but it is a gradient pixel. */ in_gsz++; /* Increment the gradient pixel count. */ lres[a] = 2; /* Flag pixel as gradient. */ } } } else { if ((!limask[pix_nextCol] && lomask[pix_nextCol]) || (!limask[pix_prevCol] && lomask[pix_prevCol]) || (!limask[pix_nextRow] && lomask[pix_nextRow]) || (!limask[pix_prevRow] && lomask[pix_prevRow])) { in_isz++; /* Increment the inner boundary pixel count. */ lres[a] = 4; /* Flag pixel as part of inner edge. */ } else { res[a] = 1.0f; /* Pixel is part of inner mask, but not at an edge. */ } } a--; pix_prevRow--; /* Advance all four "surrounding" pixel pointers. */ pix_nextRow--; pix_prevCol--; pix_nextCol--; } } rsize[0] = in_isz; /* Fill in our return sizes for edges + fill. */ rsize[1] = in_osz; rsize[2] = in_gsz; } static void do_createEdgeLocationBuffer(uint t, uint rw, const uint *lres, float *res, ushort *gbuf, uint *inner_edge_offset, uint *outer_edge_offset, uint isz, uint gsz) { int x; /* Pixel loop counter. */ int a; /* Temporary pixel index buffer loop counter. */ uint ud; /* Unscaled edge distance. */ uint dmin; /* Minimum edge distance. */ uint rsl; /* Long used for finding fast `1.0/sqrt`. */ uint gradient_fill_offset; /* For looping inner edge pixel indexes, represents current position from offset. */ uint inner_accum = 0; /* For looping outer edge pixel indexes, represents current position from offset. */ uint outer_accum = 0; /* For looping gradient pixel indexes, represents current position from offset. */ uint gradient_accum = 0; /* Disable clang-format to prevent line-wrapping. */ /* clang-format off */ /* * Here we compute the size of buffer needed to hold (row,col) coordinates * for each pixel previously determined to be either gradient, inner edge, * or outer edge. * * Allocation is done by requesting 4 bytes "sizeof(int)" per pixel, even * though gbuf[] is declared as `(ushort *)` (2 bytes) because we don't * store the pixel indexes, we only store x,y location of pixel in buffer. * * This does make the assumption that x and y can fit in 16 unsigned bits * so if Blender starts doing renders greater than 65536 in either direction * this will need to allocate gbuf[] as uint *and allocate 8 bytes * per flagged pixel. * * In general, the buffer on-screen: * * Example: 9 by 9 pixel block * * `.` = Pixel non-white in both outer and inner mask. * `o` = Pixel white in outer, but not inner mask, adjacent to "." pixel. * `g` = Pixel white in outer, but not inner mask, not adjacent to "." pixel. * `i` = Pixel white in inner mask, adjacent to "g" or "." pixel. * `F` = Pixel white in inner mask, only adjacent to other pixels white in the inner mask. * * * ......... <----- pixel #80 * ..oooo... * .oggggo.. * .oggiggo. * .ogi_figo. * .oggiggo. * .oggggo.. * ..oooo... * pixel #00 -----> ......... * * gsz = 18 (18 "g" pixels above) * isz = 4 (4 "i" pixels above) * osz = 18 (18 "o" pixels above) * * * The memory in gbuf[] after filling will look like this: * * gradient_fill_offset (0 pixels) inner_edge_offset (18 pixels) outer_edge_offset (22 pixels) * / / / * / / / * |X Y X Y X Y X Y > <----------------> <------------------------> <----------------+ * |0 2 4 6 8 10 12 14 > ... <68 70 72 74 > ... <80 82 84 86 88 90 > ... <152 154 156 158 | <- bytes * +--------------------------------> <----------------> <------------------------> <----------------+ * |g0 g0 g1 g1 g2 g2 g3 g3 > = 0; x--) { gradient_fill_offset = x << 1; t = gbuf[gradient_fill_offset]; /* Calculate column of pixel indexed by `gbuf[x]`. */ fsz = gbuf[gradient_fill_offset + 1]; /* Calculate row of pixel indexed by `gbuf[x]`. */ dmin = 0xffffffff; /* Reset min distance to edge pixel. */ for (a = outer_edge_offset + osz - 1; a >= outer_edge_offset; a--) { /* Loop through all outer edge buffer pixels. */ ud = a << 1; dy = t - gbuf[ud]; /* Set dx to gradient pixel column - outer edge pixel row. */ dx = fsz - gbuf[ud + 1]; /* Set dy to gradient pixel row - outer edge pixel column. */ ud = dx * dx + dy * dy; /* Compute sum of squares. */ if (ud < dmin) { /* If our new sum of squares is less than the current minimum. */ dmin = ud; /* Set a new minimum equal to the new lower value. */ } } odist = float(dmin); /* Cast outer min to a float. */ rsf = odist * 0.5f; rsl = *(uint *)&odist; /* Use some peculiar properties of the way bits are stored. */ rsl = 0x5f3759df - (rsl >> 1); /* In floats vs. uints to compute an approximate. */ odist = *(float *)&rsl; /* Reciprocal square root. */ odist = odist * (rsopf - (rsf * odist * odist)); /* -- This line can be iterated for more accuracy. -- */ dmin = 0xffffffff; /* Reset min distance to edge pixel. */ for (a = inner_edge_offset + isz - 1; a >= inner_edge_offset; a--) { /* Loop through all inside edge pixels. */ ud = a << 1; dy = t - gbuf[ud]; /* Compute delta in Y from gradient pixel to inside edge pixel. */ dx = fsz - gbuf[ud + 1]; /* Compute delta in X from gradient pixel to inside edge pixel. */ ud = dx * dx + dy * dy; /* Compute sum of squares. */ if (ud < dmin) { /* If our new sum of squares is less than the current minimum we've found. */ dmin = ud; /* Set a new minimum equal to the new lower value. */ } } /* Cast inner min to a float. */ idist = float(dmin); rsf = idist * 0.5f; rsl = *(uint *)&idist; /* See notes above. */ rsl = 0x5f3759df - (rsl >> 1); idist = *(float *)&rsl; idist = idist * (rsopf - (rsf * idist * idist)); /* NOTE: once again that since we are using reciprocals of distance values our * proportion is already the correct intensity, and does not need to be * subtracted from 1.0 like it would have if we used real distances. */ /* Here we reconstruct the pixel's memory location in the CompBuf by * `Pixel Index = Pixel Column + ( Pixel Row * Row Width )`. */ res[gbuf[gradient_fill_offset + 1] + (gbuf[gradient_fill_offset] * rw)] = (idist / (idist + odist)); /* Set intensity. */ } } /* End of copy. */ void DoubleEdgeMaskOperation::do_double_edge_mask(float *imask, float *omask, float *res) { uint *lres; /* Pointer to output pixel buffer (for bit operations). */ uint *limask; /* Pointer to inner mask (for bit operations). */ uint *lomask; /* Pointer to outer mask (for bit operations). */ int rw; /* Pixel row width. */ int t; /* Total number of pixels in buffer - 1 (used for loop starts). */ int fsz; /* Size of the frame. */ uint isz = 0; /* Size (in pixels) of inside edge pixel index buffer. */ uint osz = 0; /* Size (in pixels) of outside edge pixel index buffer. */ uint gsz = 0; /* Size (in pixels) of gradient pixel index buffer. */ uint rsize[3]; /* Size storage to pass to helper functions. */ uint inner_edge_offset = 0; /* Offset into final buffer where inner edge pixel indexes start. */ uint outer_edge_offset = 0; /* Offset into final buffer where outer edge pixel indexes start. */ ushort *gbuf; /* Gradient/inner/outer pixel location index buffer. */ if (true) { /* If both input sockets have some data coming in... */ rw = this->get_width(); /* Width of a row of pixels. */ t = (rw * this->get_height()) - 1; /* Determine size of the frame. */ memset(res, 0, sizeof(float) * (t + 1)); /* Clear output buffer (not all pixels will be written later). */ lres = (uint *)res; /* Pointer to output buffer (for bit level ops).. */ limask = (uint *)imask; /* Pointer to input mask (for bit level ops).. */ lomask = (uint *)omask; /* Pointer to output mask (for bit level ops).. */ /* * The whole buffer is broken up into 4 parts. The four CORNERS, the FIRST and LAST rows, the * LEFT and RIGHT edges (excluding the corner pixels), and all OTHER rows. * This allows for quick computation of outer edge pixels where * a screen edge pixel is marked to be gradient. * * The pixel type (gradient vs inner-edge vs outer-edge) tests change * depending on the user selected "Inner Edge Mode" and the user selected * "Buffer Edge Mode" on the node's GUI. There are 4 sets of basically the * same algorithm: * * 1.) Inner Edge -> Adjacent Only * Buffer Edge -> Keep Inside * * 2.) Inner Edge -> Adjacent Only * Buffer Edge -> Bleed Out * * 3.) Inner Edge -> All * Buffer Edge -> Keep Inside * * 4.) Inner Edge -> All * Buffer Edge -> Bleed Out * * Each version has slightly different criteria for detecting an edge pixel. */ if (adjacent_only_) { /* If "adjacent only" inner edge mode is turned on. */ if (keep_inside_) { /* If "keep inside" buffer edge mode is turned on. */ do_adjacentKeepBorders(t, rw, limask, lomask, lres, res, rsize); } else { /* "bleed out" buffer edge mode is turned on. */ do_adjacentBleedBorders(t, rw, limask, lomask, lres, res, rsize); } /* Set up inner edge, outer edge, and gradient buffer sizes after border pass. */ isz = rsize[0]; osz = rsize[1]; gsz = rsize[2]; /* Detect edges in all non-border pixels in the buffer. */ do_adjacentEdgeDetection(t, rw, limask, lomask, lres, res, rsize, isz, osz, gsz); } else { /* "all" inner edge mode is turned on. */ if (keep_inside_) { /* If "keep inside" buffer edge mode is turned on. */ do_allKeepBorders(t, rw, limask, lomask, lres, res, rsize); } else { /* "bleed out" buffer edge mode is turned on. */ do_allBleedBorders(t, rw, limask, lomask, lres, res, rsize); } /* Set up inner edge, outer edge, and gradient buffer sizes after border pass. */ isz = rsize[0]; osz = rsize[1]; gsz = rsize[2]; /* Detect edges in all non-border pixels in the buffer. */ do_allEdgeDetection(t, rw, limask, lomask, lres, res, rsize, isz, osz, gsz); } /* Set edge and gradient buffer sizes once again... * the sizes in rsize[] may have been modified * by the `do_*EdgeDetection()` function. */ isz = rsize[0]; osz = rsize[1]; gsz = rsize[2]; /* Calculate size of pixel index buffer needed. */ fsz = gsz + isz + osz; /* Allocate edge/gradient pixel index buffer. */ gbuf = (ushort *)MEM_callocN(sizeof(ushort) * fsz * 2, "DEM"); do_createEdgeLocationBuffer( t, rw, lres, res, gbuf, &inner_edge_offset, &outer_edge_offset, isz, gsz); do_fillGradientBuffer(rw, res, gbuf, isz, osz, gsz, inner_edge_offset, outer_edge_offset); /* Free the gradient index buffer. */ MEM_freeN(gbuf); } } DoubleEdgeMaskOperation::DoubleEdgeMaskOperation() { this->add_input_socket(DataType::Value); this->add_input_socket(DataType::Value); this->add_output_socket(DataType::Value); input_inner_mask_ = nullptr; input_outer_mask_ = nullptr; adjacent_only_ = false; keep_inside_ = false; flags_.complex = true; is_output_rendered_ = false; } bool DoubleEdgeMaskOperation::determine_depending_area_of_interest( rcti * /*input*/, ReadBufferOperation *read_operation, rcti *output) { if (cached_instance_ == nullptr) { rcti new_input; new_input.xmax = this->get_width(); new_input.xmin = 0; new_input.ymax = this->get_height(); new_input.ymin = 0; return NodeOperation::determine_depending_area_of_interest(&new_input, read_operation, output); } return false; } void DoubleEdgeMaskOperation::init_execution() { input_inner_mask_ = this->get_input_socket_reader(0); input_outer_mask_ = this->get_input_socket_reader(1); init_mutex(); cached_instance_ = nullptr; } void *DoubleEdgeMaskOperation::initialize_tile_data(rcti *rect) { if (cached_instance_) { return cached_instance_; } lock_mutex(); if (cached_instance_ == nullptr) { MemoryBuffer *inner_mask = (MemoryBuffer *)input_inner_mask_->initialize_tile_data(rect); MemoryBuffer *outer_mask = (MemoryBuffer *)input_outer_mask_->initialize_tile_data(rect); float *data = (float *)MEM_mallocN(sizeof(float) * this->get_width() * this->get_height(), __func__); float *imask = inner_mask->get_buffer(); float *omask = outer_mask->get_buffer(); do_double_edge_mask(imask, omask, data); cached_instance_ = data; } unlock_mutex(); return cached_instance_; } void DoubleEdgeMaskOperation::execute_pixel(float output[4], int x, int y, void *data) { float *buffer = (float *)data; int index = (y * this->get_width() + x); output[0] = buffer[index]; } void DoubleEdgeMaskOperation::deinit_execution() { input_inner_mask_ = nullptr; input_outer_mask_ = nullptr; deinit_mutex(); if (cached_instance_) { MEM_freeN(cached_instance_); cached_instance_ = nullptr; } } void DoubleEdgeMaskOperation::get_area_of_interest(int /*input_idx*/, const rcti & /*output_area*/, rcti &r_input_area) { r_input_area = this->get_canvas(); } void DoubleEdgeMaskOperation::update_memory_buffer(MemoryBuffer *output, const rcti & /*area*/, Span inputs) { if (!is_output_rendered_) { /* Ensure full buffers to work with no strides. */ MemoryBuffer *input_inner_mask = inputs[0]; MemoryBuffer *inner_mask = input_inner_mask->is_a_single_elem() ? input_inner_mask->inflate() : input_inner_mask; MemoryBuffer *input_outer_mask = inputs[1]; MemoryBuffer *outer_mask = input_outer_mask->is_a_single_elem() ? input_outer_mask->inflate() : input_outer_mask; BLI_assert(output->get_width() == this->get_width()); BLI_assert(output->get_height() == this->get_height()); /* TODO(manzanilla): Once tiled implementation is removed, use execution system to run * multi-threaded where possible. */ do_double_edge_mask(inner_mask->get_buffer(), outer_mask->get_buffer(), output->get_buffer()); is_output_rendered_ = true; if (inner_mask != input_inner_mask) { delete inner_mask; } if (outer_mask != input_outer_mask) { delete outer_mask; } } } } // namespace blender::compositor