VideoCodec/Modules/Prediction/MotionEstimation.cpp

#include "MotionEstimation.h" namespace VideoCodec {    MotionEstimation::MotionEstimation() : Module(MOTION_ESTIMATION_MODULE_ID)    {    }        MotionEstimation::~MotionEstimation()    {    }        ConfigurationStatus MotionEstimation::Configure(ConfigurationElement* configuration)    {       // SAE limit stopping condition.       if (configuration != NULL && configuration->GetValue("saeLimit") != NULL && configuration->GetValue("saeLimit")->GetType() == CONFIGURATION_INTEGER)          this->saeLimit = *(configuration->GetValue("saeLimit")->GetInteger());       else          this->saeLimit = 500;                 // Distance of search.       if (configuration != NULL && configuration->GetValue("distance") != NULL && configuration->GetValue("distance")->GetType() == CONFIGURATION_INTEGER)          this->distance = *(configuration->GetValue("distance")->GetInteger());       else          this->distance = 8;                 // Whether to search sub-pixel.       if (configuration != NULL && configuration->GetValue("subPixel") != NULL && configuration->GetValue("subPixel")->GetType() == CONFIGURATION_STRING)          this->subPixel = (*configuration->GetValue("subPixel")->GetString() == std::string("true"));       else          this->subPixel = true;              // This will be enabled by the pipeline if required.       this->mv4 = false;              return MODULE_CONFIGURATION_OK;    }        void MotionEstimation::ApplyMotionVectors(PictureInfo* subject, SignedByte* xComponents, SignedByte* yComponents)    {       // Use the specified vectors and pixels on the source instance to set pixel data on the destination.       if (subject->GetPixelLayout() != PIXEL_LAYOUT_SCAN)       {          Logger::LogMessage("Warning: Motion vector application requires scan order pixel data.");          return;       }              UnsignedByte* sourceData = subject->GetUnsignedByteData();       UnsignedByte* destinationData = new UnsignedByte[subject->GetPixelsX() * subject->GetPixelsY() * 3];       UnsignedByte blockRows = subject->GetBlockRows();       UnsignedByte blockColumns = subject->GetBlockColumns();       PixelCount stride = subject->GetPixelsX();              for (UnsignedByte blockY = 0; blockY < blockRows; blockY++)       {          for (UnsignedByte blockX = 0; blockX < blockColumns; blockX++)          {             for (unsigned char mvI = 0; mvI < ((this->mv4) ? 4 : 1); mvI++)             {                // The vector tells us where the pixel data for each block should come from.                SignedByte xDelta = xComponents[this->mv4 ? (blockX + blockY * blockColumns) * 4 + mvI : (blockX + blockY * blockColumns)];                SignedByte yDelta = yComponents[this->mv4 ? (blockX + blockY * blockColumns) * 4 + mvI : (blockX + blockY * blockColumns)];                                // This will only ever apply at the encoder. The decoder has its values scaled by 2 at decoding if half pixel is off.                if (!this->subPixel)                {                   xDelta *= 2;                   yDelta *= 2;                }                                SignedByte sx = 0; SignedByte sy = 0;                bool xNegative = xDelta < 0; bool yNegative = yDelta < 0;                UnsignedByte xMagnitude = xNegative ? -1 * xDelta : xDelta; UnsignedByte yMagnitude = yNegative ? -1 * yDelta : yDelta;                if ((xMagnitude & 1) != 0)                   sx = (xNegative) ? -1 : 1;                if ((yMagnitude & 1) != 0)                   sy = (yNegative) ? -1 : 1;                xDelta = xDelta / 2;                yDelta = yDelta / 2;                UnsignedByte xO = ((mvI == 1 || mvI == 3) ? 8 : 0);                UnsignedByte yO = ((mvI == 2 || mvI == 3) ? 8 : 0);                          // Translates a block. Use an appropriate expression as curExpr to apply sub-pixel translation. #define translate(curExpr,bSize,bxExpr,byExpr) \   for (UnsignedByte by = 0; by < bSize; by++)\   {\      for (UnsignedByte bx = 0; bx < bSize; bx++)\      {\         for (UnsignedByte componentIndex = 0; componentIndex < 3; componentIndex++)\         {\            destinationData[(blockX * 16 + bxExpr + stride * (blockY * 16 + byExpr)) * 3 + componentIndex] = (curExpr);\         }\      }\   };                // Integral pixel number.                if (sx == 0 && sy == 0)                {                   if (!this->mv4)                   {                      translate((sourceData[(blockX * 16 + bx + xDelta + stride * (blockY * 16 + by - yDelta)) * 3 + componentIndex]), 16, bx, by);                   }                   else                   {                      translate((sourceData[(blockX * 16 + bx + xO + xDelta + stride * (blockY * 16 + by + yO - yDelta)) * 3 + componentIndex]), 8, bx + xO, by + yO);                   }                }                // E                if (sx == 1 && sy == 0)                {                   if (!this->mv4)                   {                      translate((                            sourceData[(blockX * 16 + bx + xDelta + stride * (blockY * 16 + by - yDelta)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + bx + 1 + xDelta + stride * (blockY * 16 + by - yDelta)) * 3 + componentIndex]                             ) >> 1, 16, bx, by);                   }                   else                   {                      translate((                            sourceData[(blockX * 16 + bx + xO + xDelta + stride * (blockY * 16 + by + yO - yDelta)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + bx + xO + 1 + xDelta + stride * (blockY * 16 + by + yO - yDelta)) * 3 + componentIndex]                             ) >> 1, 8, bx + xO, by + yO);                   }                }                // SE                else if (sx == 1 && sy == 1)                {                   if (!this->mv4)                   {                      translate((                            sourceData[(blockX * 16 + bx + xDelta + stride * (blockY * 16 + by - yDelta)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + bx + 1 + xDelta + stride * (blockY * 16 + by - yDelta)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + bx + 1 + xDelta + stride * (blockY * 16 + by - yDelta - 1)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + bx + xDelta + stride * (blockY * 16 + by - yDelta - 1)) * 3 + componentIndex]                             ) >> 2, 16, bx, by);                   }                   else                   {                      translate((                            sourceData[(blockX * 16 + xO + bx + xDelta + stride * (blockY * 16 + yO + by - yDelta)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + xO + bx + 1 + xDelta + stride * (blockY * 16 + yO + by - yDelta)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + xO + bx + 1 + xDelta + stride * (blockY * 16 + yO + by - yDelta - 1)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + xO + bx + xDelta + stride * (blockY * 16 + yO + by - yDelta - 1)) * 3 + componentIndex]                             ) >> 2, 8, bx + xO, by + yO);                   }                }                // S                else if (sx == 0 && sy == 1)                {                   if (!this->mv4)                   {                      translate((                            sourceData[(blockX * 16 + bx + xDelta + stride * (blockY * 16 + by - yDelta - 1)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + bx + xDelta + stride * (blockY * 16 + by - yDelta)) * 3 + componentIndex]                             ) >> 1, 16, bx, by);                   }                   else                   {                      translate((                            sourceData[(blockX * 16 + xO + bx + xDelta + stride * (blockY * 16 + yO + by - yDelta - 1)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + xO + bx + xDelta + stride * (blockY * 16 + yO + by - yDelta)) * 3 + componentIndex]                             ) >> 1, 8, bx + xO, by + yO);                   }                }                // SW                else if (sx == -1 && sy == 1)                {                   if (!this->mv4)                   {                      translate((                            sourceData[(blockX * 16 + bx + xDelta + stride * (blockY * 16 + by - yDelta)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + bx - 1 + xDelta + stride * (blockY * 16 + by - yDelta)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + bx - 1 + xDelta + stride * (blockY * 16 + by - yDelta - 1)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + bx + xDelta + stride * (blockY * 16 + by - yDelta - 1)) * 3 + componentIndex]                             ) >> 2, 16, bx, by);                   }                   else                   {                      translate((                            sourceData[(blockX * 16 + xO + bx + xDelta + stride * (blockY * 16 + yO + by - yDelta)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + xO + bx - 1 + xDelta + stride * (blockY * 16 + yO + by - yDelta)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + xO + bx - 1 + xDelta + stride * (blockY * 16 + yO + by - yDelta - 1)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + xO + bx + xDelta + stride * (blockY * 16 + yO + by - yDelta - 1)) * 3 + componentIndex]                             ) >> 2, 8, bx + xO, by + yO);                   }                }                // W                else if (sx == -1 && sy == 0)                {                   if (!this->mv4)                   {                      translate((                            sourceData[(blockX * 16 + bx + xDelta + stride * (blockY * 16 + by - yDelta)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + bx - 1 + xDelta + stride * (blockY * 16 + by - yDelta)) * 3 + componentIndex]                             ) >> 1, 16, bx, by);                   }                   else                   {                      translate((                            sourceData[(blockX * 16 + xO + bx + xDelta + stride * (blockY * 16 + yO + by - yDelta)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + xO + bx - 1 + xDelta + stride * (blockY * 16 + yO + by - yDelta)) * 3 + componentIndex]                             ) >> 1, 8, bx + xO, by + yO);                   }                }                // NW                else if (sx == -1 && sy == -1)                {                   if (!this->mv4)                   {                      translate((                            sourceData[(blockX * 16 + bx + xDelta + stride * (blockY * 16 + by - yDelta)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + bx - 1 + xDelta + stride * (blockY * 16 + by - yDelta)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + bx - 1 + xDelta + stride * (blockY * 16 + by - yDelta + 1)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + bx + xDelta + stride * (blockY * 16 + by - yDelta + 1)) * 3 + componentIndex]                             ) >> 2, 16, bx, by);                   }                   else                   {                      translate((                            sourceData[(blockX * 16 + xO + bx + xDelta + stride * (blockY * 16 + yO + by - yDelta)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + xO + bx - 1 + xDelta + stride * (blockY * 16 + yO + by - yDelta)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + xO + bx - 1 + xDelta + stride * (blockY * 16 + yO + by - yDelta + 1)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + xO + bx + xDelta + stride * (blockY * 16 + yO + by - yDelta + 1)) * 3 + componentIndex]                             ) >> 2, 8, bx + xO, by + yO);                   }                }                // N                else if (sx == 0 && sy == -1)                {                   if (!this->mv4)                   {                      translate((                            sourceData[(blockX * 16 + bx + xDelta + stride * (blockY * 16 + by - yDelta)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + bx + xDelta + stride * (blockY * 16 + by - yDelta + 1)) * 3 + componentIndex]                             ) >> 1, 16, bx, by);                   }                   else                   {                      translate((                            sourceData[(blockX * 16 + xO + bx + xDelta + stride * (blockY * 16 + yO + by - yDelta)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + xO + bx + xDelta + stride * (blockY * 16 + yO + by - yDelta + 1)) * 3 + componentIndex]                             ) >> 1, 8, bx + xO, by + yO);                   }                }                // NE                else if (sx == 1 && sy == -1)                {                   if (!this->mv4)                   {                      translate((                            sourceData[(blockX * 16 + bx + xDelta + stride * (blockY * 16 + by - yDelta)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + bx + 1 + xDelta + stride * (blockY * 16 + by - yDelta)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + bx + 1 + xDelta + stride * (blockY * 16 + by - yDelta + 1)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + bx + xDelta + stride * (blockY * 16 + by - yDelta + 1)) * 3 + componentIndex]                             ) >> 2, 16, bx, by);                   }                   else                   {                      translate((                            sourceData[(blockX * 16 + xO + bx + xDelta + stride * (blockY * 16 + yO + by - yDelta)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + xO + bx + 1 + xDelta + stride * (blockY * 16 + yO + by - yDelta)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + xO + bx + 1 + xDelta + stride * (blockY * 16 + yO + by - yDelta + 1)) * 3 + componentIndex] +                            sourceData[(blockX * 16 + xO + bx + xDelta + stride * (blockY * 16 + yO + by - yDelta + 1)) * 3 + componentIndex]                             ) >> 2, 8, bx + xO, by + yO);                   }                } #undef translate             }          }       }              subject->SetUnsignedByteData(destinationData, subject->GetPixelsX() * subject->GetPixelsY() * 3);    }        void MotionEstimation::CalculateMotionVectors(PictureInfo* reference, PictureInfo* current)    {       // We are trying to derive one vector for each block in current, positioned at the relevant block's       // centre, which gives an offset from the same block in the reference picture to a pixel block       // which has a lowest difference value based on the chosen metric.              // Here we use the sum of absolute differences metric, based on values in the luma channel.       // Also, we assume 16 * 16 macroblocks in the luma channel.              // 20th April 2007: Added half-pixel estimation.              // Make sure the input is in the correct format.       if (reference->GetPixelLayout() != PIXEL_LAYOUT_SCAN)       {          Logger::LogMessage("Warning: Motion vector calculation requires scan order pixel data.");          return;       }              if (reference->GetPixelsX() != current->GetPixelsX() || reference->GetPixelsY() != current->GetPixelsY())       {          Logger::LogMessage("Warning: Mismatched reference picture dimensions in motion vector calculation.");          return;       }              BlockCount blockColumns = reference->GetPixelsX() >> 4;       BlockCount blockRows = reference->GetPixelsY() >> 4;       UnsignedShort imageWidth = reference->GetPixelsX();       UnsignedByte* referenceData = reference->GetUnsignedByteData();       UnsignedByte* currentData = current->GetUnsignedByteData();              // Allocate memory to store the displacements       SignedByte* xDisplacements = new SignedByte[(this->mv4 ? 4 : 1) * blockColumns * blockRows];       SignedByte* yDisplacements = new SignedByte[(this->mv4 ? 4 : 1) * blockColumns * blockRows];              // Currently we use the sum of absolute errors (SAE) measure here.       for (BlockCount by = 0; by < blockRows; by++)       {          for (BlockCount bx = 0; bx < blockColumns; bx++)          {             for (unsigned char mvI = 0; mvI < (this->mv4 ? 4 : 1); mvI++)             {                SignedByte away, yDelta, xDelta;                unsigned char bs = (this->mv4 ? 8 : 16);                unsigned char xO = ((mvI == 0 || mvI == 2) ? 0 : 8);                unsigned char yO = ((mvI == 0 || mvI == 1) ? 0 : 8);                                // Calculate for (0, 0) displacement.                UnsignedShort thisSAE = this->sumAbsoluteError(                      referenceData + ((by * 16 + yO) * 3 * imageWidth) + ((bx * 16 + xO) * 3),                      currentData + ((by * 16 + yO) * 3 * imageWidth) + ((bx * 16 + xO) * 3),                      imageWidth, bs);                                // Store the best current vector and its SAE value.                SignedByte bestXDisplacement = 0;                SignedByte bestYDisplacement = 0;                UnsignedShort bestSAE = thisSAE;                                // Spiral scan pattern. Break if there is a perfect match.                for (away = 1; away < this->distance && bestSAE > this->saeLimit; away++)                {                   // Check we are within bounds.                   yDelta = -away;                   if (by > 0)                      for (xDelta = -away; xDelta < away; xDelta++)                      {                         if (bx == 0 && xDelta < 0)                            xDelta = 0;                         if (bx == blockColumns - 1 && xDelta > 0)                            break;                                                  // Calculate the SAE and add it on.                         thisSAE = this->sumAbsoluteError(                               referenceData + ((by * 16 + yDelta + yO) * 3 * imageWidth) + ((bx * 16 + xDelta + xO) * 3),                               currentData + ((by * 16 + yO) * 3 * imageWidth) + (((bx + xO) * 16) * 3),                               imageWidth, bs);                                                  if (thisSAE < bestSAE)                         {                            bestSAE = thisSAE;                            bestXDisplacement = xDelta;                            bestYDisplacement = yDelta;                         }                      }                   else                      xDelta = away;                                      if (bx < blockColumns - 1)                      for (yDelta = -away; yDelta < away; yDelta++)                      {                         if (by == 0 && yDelta < 0)                            yDelta = 0;                         if (by == blockRows - 1 && yDelta > 0)                            break;                                                  thisSAE = this->sumAbsoluteError(                               referenceData + ((by * 16 + yDelta + yO) * 3 * imageWidth) + ((bx * 16 + xDelta + xO) * 3),                               currentData + ((by * 16 + yO) * 3 * imageWidth) + ((bx * 16 + xO) * 3),                               imageWidth, bs);                                                  if (thisSAE < bestSAE)                         {                            bestSAE = thisSAE;                            bestXDisplacement = xDelta;                            bestYDisplacement = yDelta;                         }                      }                   else                      yDelta = away;                                      if (by < blockRows - 1)                      for (xDelta = away; xDelta > -away; xDelta--)                      {                         if (bx == blockColumns - 1 && xDelta > 0)                            xDelta = 0;                         if (bx == 0 && xDelta < 0)                            break;                                                  thisSAE = this->sumAbsoluteError(                               referenceData + ((by * 16 + yDelta + yO) * 3 * imageWidth) + ((bx * 16 + xDelta + xO) * 3),                               currentData + ((by * 16 + yO) * 3 * imageWidth) + ((bx * 16 + xO) * 3),                               imageWidth, bs);                                                  if (thisSAE < bestSAE)                         {                            bestSAE = thisSAE;                            bestXDisplacement = xDelta;                            bestYDisplacement = yDelta;                         }                      }                   else                      xDelta = -away;                                      if (bx > 0)                      for (yDelta = away; yDelta > -away; yDelta--)                      {                         if (by == blockRows - 1 && yDelta > 0)                            yDelta = 0;                         if (by == 0 && yDelta < 0)                            break;                                                  thisSAE = this->sumAbsoluteError(                               referenceData + ((by * 16 + yDelta + yO) * 3 * imageWidth) + ((bx * 16 + xDelta + xO) * 3),                               currentData + ((by * 16 + yO) * 3 * imageWidth) + ((bx * 16 + xO) * 3),                               imageWidth, bs);                                                  if (thisSAE < bestSAE)                         {                            bestSAE = thisSAE;                            bestXDisplacement = xDelta;                            bestYDisplacement = yDelta;                         }                      }                   else                      yDelta = -away;                }                                SignedByte dx = bestXDisplacement;                SignedByte dy = bestYDisplacement;                                SignedByte subXDelta = 0;                SignedByte subYDelta = 0;                if (this->subPixel)                {                   // A macro to make the sum for a block SAE more concise.                   // refExpr should be a function x and y which are the coordinates in the block.    #define sae(refExpr,curExpr) \      for (unsigned char y = 0; y < bs; y++)\      {\         for (unsigned char x = 0; x < bs; x++)\         {\            SignedShort delta = (refExpr) - (curExpr);\            saeValue += (delta < 0 ? -delta : delta);\         }\      };                       // We now refine these values using half pixel motion estimation.                   // sae(referenceData[((by * 16 + y) * 3 * imageWidth) + ((bx * 16 + x) * 3)],currentData[((by * 16) * 3 * imageWidth) + ((bx * 16) * 3)]);                   UnsignedShort saeValue = 0;                   // E                   sae((referenceData[((by * 16 + dy + y + yO) * 3 * imageWidth) + ((bx * 16 + dx + x + xO) * 3)] + referenceData[((by * 16 + dy + y + yO) * 3 * imageWidth) + ((bx * 16 + dx + x + 1 + xO) * 3)]) >> 1,currentData[((by * 16 + yO) * 3 * imageWidth) + ((bx * 16 + xO) * 3)]);                   if (saeValue < bestSAE) { bestSAE = saeValue; subXDelta = 1; subYDelta = 0;   }                   saeValue = 0;                   // SE                   sae((referenceData[((by * 16 + dy + y + yO) * 3 * imageWidth) + ((bx * 16 + dx + x + xO) * 3)] + referenceData[((by * 16 + dy + y + yO) * 3 * imageWidth) + ((bx * 16 + dx + x + 1 + xO) * 3)] + referenceData[((by * 16 + dy + y + 1 + yO) * 3 * imageWidth) + ((bx * 16 + dx + x + 1 + xO) * 3)] + referenceData[((by * 16 + dy + y + 1 + yO) * 3 * imageWidth) + ((bx * 16 + dx + x + xO) * 3)]) >> 2,currentData[((by * 16 + yO) * 3 * imageWidth) + ((bx * 16 + xO) * 3)]);                   if (saeValue < bestSAE) { bestSAE = saeValue; subXDelta = 1; subYDelta = 1;   }                   saeValue = 0;                   // S                   sae((referenceData[((by * 16 + dy + y + yO) * 3 * imageWidth) + ((bx * 16 + dx + x + xO) * 3)] + referenceData[((by * 16 + dy + y + 1 + yO) * 3 * imageWidth) + ((bx * 16 + dx + x + xO) * 3)]) >> 1,currentData[((by * 16 + yO) * 3 * imageWidth) + ((bx * 16 + xO) * 3)]);                   if (saeValue < bestSAE) { bestSAE = saeValue; subXDelta = 0; subYDelta = 1;   }                   saeValue = 0;                   // SW                   sae((referenceData[((by * 16 + dy + y + yO) * 3 * imageWidth) + ((bx * 16 + dx + x + xO) * 3)] + referenceData[((by * 16 + dy + y + 1 + yO) * 3 * imageWidth) + ((bx * 16 + dx + x + xO) * 3)] + referenceData[((by * 16 + dy + y + 1 + yO) * 3 * imageWidth) + ((bx * 16 + dx + x - 1 + xO) * 3)] + referenceData[((by * 16 + dy + y + yO) * 3 * imageWidth) + ((bx * 16 + dx + x - 1 + xO) * 3)]) >> 2,currentData[((by * 16 + yO) * 3 * imageWidth) + ((bx * 16 + xO) * 3)]);                   if (saeValue < bestSAE) { bestSAE = saeValue; subXDelta = -1; subYDelta = 1; }                   saeValue = 0;                   // W                   sae((referenceData[((by * 16 + dy + y + yO) * 3 * imageWidth) + ((bx * 16 + dx + x + xO) * 3)] + referenceData[((by * 16 + dy + y + yO) * 3 * imageWidth) + ((bx * 16 + dx + x - 1 + xO) * 3)]) >> 1,currentData[((by * 16 + yO) * 3 * imageWidth) + ((bx * 16 + xO) * 3)]);                   if (saeValue < bestSAE) { bestSAE = saeValue; subXDelta = -1; subYDelta = 0; }                   saeValue = 0;                   // NW                   sae((referenceData[((by * 16 + dy + y + yO) * 3 * imageWidth) + ((bx * 16 + dx + x + xO) * 3)] + referenceData[((by * 16 + dy + y - 1 + yO) * 3 * imageWidth) + ((bx * 16 + dx + x + xO) * 3)] + referenceData[((by * 16 + dy + y - 1 + yO) * 3 * imageWidth) + ((bx * 16 + dx + x - 1 + xO) * 3)] + referenceData[((by * 16 + dy + y + yO) * 3 * imageWidth) + ((bx * 16 + dx + x - 1 + xO) * 3)]) >> 2,currentData[((by * 16 + yO) * 3 * imageWidth) + ((bx * 16 + xO) * 3)]);                   if (saeValue < bestSAE) { bestSAE = saeValue; subXDelta = -1; subYDelta = -1; }                   saeValue = 0;                   // N                   sae((referenceData[((by * 16 + dy + y + yO) * 3 * imageWidth) + ((bx * 16 + dx + x + xO) * 3)] + referenceData[((by * 16 + dy + y - 1 + yO) * 3 * imageWidth) + ((bx * 16 + dx + x + xO) * 3)]) >> 1,currentData[((by * 16 + yO) * 3 * imageWidth) + ((bx * 16 + xO) * 3)]);                   if (saeValue < bestSAE) { bestSAE = saeValue; subXDelta = 0; subYDelta = -1; }                   saeValue = 0;                   // NE                   sae((referenceData[((by * 16 + dy + y + yO) * 3 * imageWidth) + ((bx * 16 + dx + x + xO) * 3)] + referenceData[((by * 16 + dy + y + yO) * 3 * imageWidth) + ((bx * 16 + dx + x + 1 + xO) * 3)] + referenceData[((by * 16 + dy + y - 1 + yO) * 3 * imageWidth) + ((bx * 16 + dx + x + 1 + xO) * 3)] + referenceData[((by * 16 + dy + y - 1 + yO) * 3 * imageWidth) + ((bx * 16 + dx + x + xO) * 3)]) >> 2,currentData[((by * 16 + yO) * 3 * imageWidth) + ((bx * 16 + xO) * 3)]);                   if (saeValue < bestSAE) { bestSAE = saeValue; subXDelta = 1; subYDelta = -1; } #undef sae                }                                bool negative = (bestXDisplacement < 0);                UnsignedByte magnitude = negative ? (-bestXDisplacement) : (bestXDisplacement);                xDisplacements[this->mv4 ? (bx + by * blockColumns) * 4 + mvI : bx + by * blockColumns] = ((this->subPixel ? (magnitude << 1) : magnitude) + (subXDelta < 0 ? -subXDelta : subXDelta)) * (negative ? -1 : 1);                                // The Y displacement vector is reflected vertically for convenience of display.                negative = (bestYDisplacement < 0);                magnitude = (negative) ? (-bestYDisplacement) : (bestYDisplacement);                yDisplacements[this->mv4 ? (bx + by * blockColumns) * 4 + mvI : bx + by * blockColumns] = -(((this->subPixel ? (magnitude << 1) : magnitude) + (subYDelta < 0 ? -subYDelta : subYDelta)) * (negative ? -1 : 1));             }          }       }              current->SetMVDisplacementsX(xDisplacements, blockColumns * blockRows);       current->SetMVDisplacementsY(yDisplacements, blockColumns * blockRows);    }        inline UnsignedShort MotionEstimation::sumAbsoluteError(UnsignedByte* referenceDataStart, UnsignedByte* currentStart, UnsignedShort imageWidth, unsigned char blockSize)    {       UnsignedShort sae = 0;       for (unsigned char y = 0; y < blockSize; y++)       {          for (unsigned char x = 0; x < blockSize; x++)          {             SignedShort delta = referenceDataStart[x * 3] - currentStart[x * 3];             sae += (delta < 0 ? -delta : delta);          }                    // Stride to the next row.          referenceDataStart += imageWidth * 3;          currentStart += imageWidth * 3;       }              return sae;    }        void MotionEstimation::EnableMV4()    {       this->mv4 = true;    }        bool MotionEstimation::UseSubPixel()    {       return this->subPixel;    } }