]> granicus.if.org Git - imagemagick/commitdiff
[DO NOT MERGE YET] Add More Pixel Art Upscaling Algorithms (#1152)
authorMiloslav Číž #movingToGitLab <tastyfish@seznam.cz>
Sat, 13 Jul 2019 13:45:53 +0000 (15:45 +0200)
committerImageMagick <urban-warrior@users.noreply.github.com>
Sat, 13 Jul 2019 13:45:53 +0000 (09:45 -0400)
* Start extending magnify function

* Fix pixel alignment

* Reimplement scale2x in a new function

* Create IntensitiesEqual function

* Implement eagle algorithm

* Retrieve magnification alg from -define

* Make use of -define with -magnify

* Make use of magnification variable

* alg -> method

* Implement scale3x

* Implement eagle3x

* Compare pixels by channels

* Implement eagle3xb

* Implement EPX

* Start implementing hq2x

* Fix hq2x

* Fix formatting

* Add const qualifiers

* epx -> epbx

* Implement XBR

* Fix XBR

* Implement fish

* Fix indexed color bug

* Refactor

MagickCore/resize.c

index 2c1d4bf997db8bb7e2e95a3d23f33c7106b2a6c5..6a56f6edc927666388937ebac4ab1857e67ae26d 100644 (file)
@@ -2035,6 +2035,662 @@ MagickExport Image *LiquidRescaleImage(const Image *image,
 }
 #endif
 \f
+
+void CopyPixel(const Quantum *src,size_t src_off,Quantum *dst,size_t dst_off,
+  size_t channels)
+{
+  register ssize_t
+    i;
+
+  for (i = 0; i < channels; i++)
+    dst[channels*dst_off+i] = src[src_off*channels+i];
+}
+
+/*
+  Helper function for pixel art scaling.
+*/
+void MixPixels(const Quantum *src,size_t *src_offs,size_t src_offs_size,
+  Quantum *dst,size_t dst_off,size_t channels)
+{
+  int sum;
+
+  register ssize_t
+    i, j;
+  
+  for (i = 0; i < channels; i++)
+  {
+    sum = 0;
+
+    for (j = 0; j < src_offs_size; j++)
+      sum += src[src_offs[j] * channels + i];
+
+    dst[channels*dst_off+i] = sum / src_offs_size;
+  }
+}
+
+void Mix2Pixels(const Quantum *src,size_t src_off1,size_t src_off2,Quantum *dst,
+  size_t dst_off,size_t channels)
+{
+  MixPixels(src,(size_t[2]){src_off1,src_off2},2,dst,dst_off,channels);
+}
+
+int PixelsEqual(const Quantum *src1,size_t off1,const Quantum *src2,size_t off2,
+  size_t channels)
+{
+  register ssize_t
+    i;
+
+  off1 *= channels;
+  off2 *= channels;
+
+  for (i=0; i<channels; i++)
+    if (src1[off1+i] != src2[off2+i])
+      return 0;
+
+  return 1;
+}
+
+void Eagle2X(const Image *src_image,const Quantum *neighbourhood,
+  Quantum *result,size_t channels)
+{
+  for (unsigned char i=0; i<4; i++)
+    CopyPixel(neighbourhood,4,result,i,channels);
+
+  if (PixelsEqual(neighbourhood,0,neighbourhood,1,channels) &&
+      PixelsEqual(neighbourhood,1,neighbourhood,3,channels))
+    CopyPixel(neighbourhood,0,result,0,channels);
+
+  if (PixelsEqual(neighbourhood,1,neighbourhood,2,channels) &&
+      PixelsEqual(neighbourhood,2,neighbourhood,5,channels))
+    CopyPixel(neighbourhood,2,result,1,channels);
+
+  if (PixelsEqual(neighbourhood,3,neighbourhood,6,channels) &&
+      PixelsEqual(neighbourhood,6,neighbourhood,7,channels))
+    CopyPixel(neighbourhood,6,result,2,channels);
+
+  if (PixelsEqual(neighbourhood,5,neighbourhood,8,channels) &&
+      PixelsEqual(neighbourhood,8,neighbourhood,7,channels))
+    CopyPixel(neighbourhood,8,result,3,channels);
+}
+
+void Hq2XHelper(unsigned int rule,const Quantum *src_image,Quantum *dst_image,
+  size_t dst_off,size_t channels,
+  size_t e,
+  size_t a,
+  size_t b,
+  size_t d,
+  size_t f,
+  size_t h)
+{
+#define caseA(N,A,B,C,D)\
+case N:\
+MixPixels(src_image,(size_t[4]){A,B,C,D},4,dst_image,dst_off,channels);\
+break;
+
+#define caseB(N,A,B,C,D,E,F,G,H)\
+case N:\
+MixPixels(src_image,(size_t[8]){A,B,C,D,E,F,G,H},8,dst_image,dst_off,channels);\
+break;
+
+  switch (rule)
+  {
+    case 0:
+      CopyPixel(src_image,e,dst_image,dst_off,channels);
+      break;
+
+    caseA(1, e,e,e,a)
+    caseA(2, e,e,e,d)
+    caseA(3, e,e,e,b)
+    caseA(4, e,e,d,b)
+    caseA(5, e,e,a,b)
+    caseA(6, e,e,a,d)
+    caseB(7, e,e,e,e,e,b,b,d)
+    caseB(8, e,e,e,e,e,d,d,b)
+    caseB(9, e,e,e,e,e,e,d,b)
+    caseB(10,e,e,d,d,d,b,b,b)
+    
+    case 11:
+      MixPixels(src_image,(size_t[16]){e,e,e,e,e,e,e,e,e,e,e,e,e,e,d,b},16,
+        dst_image,dst_off,channels);
+      break;
+
+    case 12:
+      if (PixelsEqual(src_image,b,src_image,d,channels))
+        MixPixels(src_image,(size_t[4]){e,e,d,b},4,dst_image,dst_off,
+          channels);
+      else
+        CopyPixel(src_image,e,dst_image,dst_off,channels);
+
+      break;
+
+    case 13:
+      if (PixelsEqual(src_image,b,src_image,d,channels))
+        MixPixels(src_image,(size_t[8]){e,e,d,d,d,b,b,b},8,dst_image,dst_off,
+          channels);
+      else
+        CopyPixel(src_image,e,dst_image,dst_off,channels);
+
+      break;
+
+    case 14:
+      if (PixelsEqual(src_image,b,src_image,d,channels))
+        MixPixels(src_image,(size_t[16]){e,e,e,e,e,e,e,e,e,e,e,e,e,e,d,b},16,
+          dst_image,dst_off,channels);
+      else
+        CopyPixel(src_image,e,dst_image,dst_off,channels);
+
+      break;
+  
+    case 15:
+      if (PixelsEqual(src_image,b,src_image,d,channels))
+        MixPixels(src_image,(size_t[4]){e,e,d,b},4,dst_image,dst_off,
+          channels);
+      else
+        MixPixels(src_image,(size_t[4]){e,e,e,a},4,dst_image,dst_off,
+          channels);
+
+      break;
+
+    case 16:
+      if (PixelsEqual(src_image,b,src_image,d,channels))
+        MixPixels(src_image,(size_t[8]){e,e,e,e,e,e,d,b},8,dst_image,dst_off,
+          channels);
+      else
+        MixPixels(src_image,(size_t[4]){e,e,e,a},4,dst_image,dst_off,
+          channels);
+
+      break;
+
+    case 17:
+      if (PixelsEqual(src_image,b,src_image,d,channels))
+        MixPixels(src_image,(size_t[8]){e,e,d,d,d,b,b,b},8,dst_image,dst_off,
+          channels);
+      else
+        MixPixels(src_image,(size_t[4]){e,e,e,a},4,dst_image,dst_off,
+          channels);
+
+      break;
+
+    case 18:
+      if (PixelsEqual(src_image,b,src_image,f,channels))
+        MixPixels(src_image,(size_t[8]){e,e,e,e,e,b,b,d},8,dst_image,dst_off,
+          channels);
+      else
+        MixPixels(src_image,(size_t[4]){e,e,e,d},4,dst_image,dst_off,
+          channels);
+
+      break;
+
+    default:
+      if (PixelsEqual(src_image,d,src_image,h,channels))
+        MixPixels(src_image,(size_t[8]){e,e,e,e,e,d,d,b},8,dst_image,dst_off,
+          channels);
+      else
+        MixPixels(src_image,(size_t[4]){e,e,e,b},4,dst_image,dst_off,
+          channels);
+      
+      break;
+  }
+  #undef caseA
+  #undef caseB
+}
+
+unsigned int Hq2XPatternToNumber(const int *pattern)
+{
+  unsigned int
+    result=0,
+    order=1;
+
+  for (int i=7; i>=0; i--)
+  {
+    result += order * pattern[i];
+    order *= 2;
+  }
+  return result;
+}
+
+void Hq2X(const Image *src_image,const Quantum *neighbourhood,Quantum *result,
+  size_t channels)
+{
+  static const unsigned int Hq2XTable[] =
+  {
+    4, 4, 6,  2, 4, 4, 6,  2, 5,  3, 15, 12, 5,  3, 17, 13,
+    4, 4, 6, 18, 4, 4, 6, 18, 5,  3, 12, 12, 5,  3,  1, 12,
+    4, 4, 6,  2, 4, 4, 6,  2, 5,  3, 17, 13, 5,  3, 16, 14,
+    4, 4, 6, 18, 4, 4, 6, 18, 5,  3, 16, 12, 5,  3,  1, 14,
+    4, 4, 6,  2, 4, 4, 6,  2, 5, 19, 12, 12, 5, 19, 16, 12,
+    4, 4, 6,  2, 4, 4, 6,  2, 5,  3, 16, 12, 5,  3, 16, 12,
+    4, 4, 6,  2, 4, 4, 6,  2, 5, 19,  1, 12, 5, 19,  1, 14,
+    4, 4, 6,  2, 4, 4, 6, 18, 5,  3, 16, 12, 5, 19,  1, 14,
+    4, 4, 6,  2, 4, 4, 6,  2, 5,  3, 15, 12, 5,  3, 17, 13,
+    4, 4, 6,  2, 4, 4, 6,  2, 5,  3, 16, 12, 5,  3, 16, 12,
+    4, 4, 6,  2, 4, 4, 6,  2, 5,  3, 17, 13, 5,  3, 16, 14,
+    4, 4, 6,  2, 4, 4, 6,  2, 5,  3, 16, 13, 5,  3,  1, 14,
+    4, 4, 6,  2, 4, 4, 6,  2, 5,  3, 16, 12, 5,  3, 16, 13,
+    4, 4, 6,  2, 4, 4, 6,  2, 5,  3, 16, 12, 5,  3,  1, 12,
+    4, 4, 6,  2, 4, 4, 6,  2, 5,  3, 16, 12, 5,  3,  1, 14,
+    4, 4, 6,  2, 4, 4, 6,  2, 5,  3,  1, 12, 5,  3,  1, 14
+  };
+
+  const int pattern1[] = 
+  {
+    !PixelsEqual(neighbourhood,4,neighbourhood,8,channels),
+    !PixelsEqual(neighbourhood,4,neighbourhood,7,channels),
+    !PixelsEqual(neighbourhood,4,neighbourhood,6,channels),
+    !PixelsEqual(neighbourhood,4,neighbourhood,5,channels),
+    !PixelsEqual(neighbourhood,4,neighbourhood,3,channels),
+    !PixelsEqual(neighbourhood,4,neighbourhood,2,channels),
+    !PixelsEqual(neighbourhood,4,neighbourhood,1,channels),
+    !PixelsEqual(neighbourhood,4,neighbourhood,0,channels)
+  };
+
+#define Rotated(p) p[2], p[4], p[7], p[1], p[6], p[0], p[3], p[5]
+  const int pattern2[] = { Rotated(pattern1) };
+  const int pattern3[] = { Rotated(pattern2) };
+  const int pattern4[] = { Rotated(pattern3) };
+#undef Rotated
+
+  Hq2XHelper(Hq2XTable[Hq2XPatternToNumber(pattern1)],neighbourhood,result,0,
+    channels,4,0,1,3,5,7);
+
+  Hq2XHelper(Hq2XTable[Hq2XPatternToNumber(pattern2)],neighbourhood,result,1,
+    channels,4,2,5,1,7,3);
+
+  Hq2XHelper(Hq2XTable[Hq2XPatternToNumber(pattern3)],neighbourhood,result,3,
+    channels,4,8,7,5,3,1);
+
+  Hq2XHelper(Hq2XTable[Hq2XPatternToNumber(pattern4)],neighbourhood,result,2,
+    channels,4,6,3,7,1,5);
+}
+
+void Fish2X(const Image *src_image,const Quantum *neighbourhood,
+  Quantum *result,size_t channels)
+{
+  MagickFloatType intensities[9];
+
+  for (unsigned int i = 0; i < 9; i++)
+    intensities[i] = GetPixelIntensity(src_image,neighbourhood + i*channels);
+
+  CopyPixel(neighbourhood,0,result,0,channels);
+  CopyPixel(neighbourhood,intensities[0] > intensities[1] ? 0 : 1,result,1,
+    channels);
+  CopyPixel(neighbourhood,intensities[0] > intensities[3] ? 0 : 3,result,2,
+    channels);
+
+  const int ae = PixelsEqual(neighbourhood,0,neighbourhood,4,channels);
+  const int bd = PixelsEqual(neighbourhood,1,neighbourhood,3,channels);
+  const int ab = PixelsEqual(neighbourhood,0,neighbourhood,1,channels);
+  const int de = PixelsEqual(neighbourhood,3,neighbourhood,4,channels);
+  const int ad = PixelsEqual(neighbourhood,0,neighbourhood,3,channels);
+  const int be = PixelsEqual(neighbourhood,1,neighbourhood,4,channels);
+
+#define Corner(A,B,C,D)\
+if (intensities[B] > intensities[A])\
+  MixPixels(neighbourhood,(size_t[3]){B,C,D},3,result,3,channels);\
+else\
+  MixPixels(neighbourhood,(size_t[3]){A,B,C},3,result,3,channels);
+
+#define Line(A,B,C,D)\
+if (intensities[C] > intensities[A])\
+  Mix2Pixels(neighbourhood,C,D,result,3,channels);\
+else\
+  Mix2Pixels(neighbourhood,A,B,result,3,channels);\
+
+  if (ae && bd && ab)
+    CopyPixel(neighbourhood,0,result,3,channels);
+  else if (ad && de && !ab)
+    Corner(1,0,4,3)
+  else if (be && de && !ab)
+    Corner(0,1,3,4)
+  else if (ad && ab && !be)
+    Corner(4,3,1,0)
+  else if (ab && be && !ad)
+    Corner(3,0,4,1)
+  else if (ae && (!bd || intensities[1] > intensities[0]))
+    Mix2Pixels(neighbourhood,0,4,result,3,channels);
+  else if (bd && (!ae || intensities[0] > intensities[1]))
+    Mix2Pixels(neighbourhood,1,3,result,3,channels);
+  else if (ab)
+    Line(0,1,3,4)
+  else if (de)
+    Line(3,4,0,1)
+  else if (ad)
+    Line(0,3,1,4)
+  else if (be)
+    Line(1,4,0,3)
+  else
+    MixPixels(neighbourhood,(size_t[4]){0,1,3,4},4,result,3,channels);
+
+#undef Corner
+#undef Line
+}
+
+void Xbr2X(const Image *src_image,const Quantum *neighbourhood,
+  Quantum *result,size_t channels)
+{
+#define WeightVar(M,N) const int w_##M##_##N = \
+  PixelsEqual(neighbourhood,M,neighbourhood,N,channels) ? 0 : 1;
+  WeightVar(12,11)
+  WeightVar(12,7)
+  WeightVar(12,13)
+  WeightVar(12,17)
+  WeightVar(12,16)
+  WeightVar(12,8)
+  WeightVar(6,10)
+  WeightVar(6,2)
+  WeightVar(11,7)
+  WeightVar(11,17)
+  WeightVar(11,5)
+  WeightVar(7,13)
+  WeightVar(7,1)
+  WeightVar(12,6)
+  WeightVar(12,18)
+  WeightVar(8,14)
+  WeightVar(8,2)
+  WeightVar(13,17)
+  WeightVar(13,9)
+  WeightVar(7,3)
+  WeightVar(16,10)
+  WeightVar(16,22)
+  WeightVar(17,21)
+  WeightVar(11,15)
+  WeightVar(18,14)
+  WeightVar(18,22)
+  WeightVar(17,23)
+  WeightVar(17,19)
+#undef WeightVar
+  
+  if (
+    w_12_16 + w_12_8 + w_6_10 + w_6_2 + (4 * w_11_7) <
+    w_11_17 + w_11_5 + w_7_13 + w_7_1 + (4 * w_12_6)
+  )
+    Mix2Pixels(neighbourhood,w_12_11 <= w_12_7 ? 11 : 7,12,result,0,channels);    
+  else
+    CopyPixel(neighbourhood,12,result,0,channels);
+
+  if (
+    w_12_18 + w_12_6 + w_8_14 + w_8_2 + (4 * w_7_13) <
+    w_13_17 + w_13_9 + w_11_7 + w_7_3 + (4 * w_12_8)
+  )
+    Mix2Pixels(neighbourhood,w_12_7 <= w_12_13 ? 7 : 13,12,result,1,channels);    
+  else
+    CopyPixel(neighbourhood,12,result,1,channels);
+
+  if (
+    w_12_6 + w_12_18 + w_16_10 + w_16_22 + (4 * w_11_17) <
+    w_11_7 + w_11_15 + w_13_17 + w_17_21 + (4 * w_12_16)
+  )
+    Mix2Pixels(neighbourhood,w_12_11 <= w_12_17 ? 11 : 17,12,result,2,channels);    
+  else
+    CopyPixel(neighbourhood,12,result,2,channels);
+
+  if (
+    w_12_8 + w_12_16 + w_18_14 + w_18_22 + (4 * w_13_17) <
+    w_11_17 + w_17_23 + w_17_19 + w_7_13 + (4 * w_12_18)
+  )
+    Mix2Pixels(neighbourhood,w_12_13 <= w_12_17 ? 13 : 17,12,result,3,channels);    
+  else
+    CopyPixel(neighbourhood,12,result,3,channels);
+}
+
+void Scale2X(const Image *src_image,const Quantum *neighbourhood,
+  Quantum *result,size_t channels)
+{
+  if (PixelsEqual(neighbourhood,1,neighbourhood,7,channels) ||
+      PixelsEqual(neighbourhood,3,neighbourhood,5,channels))
+    {
+      for (unsigned char i=0; i<4; i++)
+        CopyPixel(neighbourhood,4,result,i,channels);
+    }
+  else
+    {
+      if (PixelsEqual(neighbourhood,1,neighbourhood,3,channels))
+        CopyPixel(neighbourhood,3,result,0,channels);
+      else
+        CopyPixel(neighbourhood,4,result,0,channels);
+
+      if (PixelsEqual(neighbourhood,1,neighbourhood,5,channels))
+        CopyPixel(neighbourhood,5,result,1,channels);
+      else
+        CopyPixel(neighbourhood,4,result,1,channels);
+
+      if (PixelsEqual(neighbourhood,3,neighbourhood,7,channels))
+        CopyPixel(neighbourhood,3,result,2,channels);
+      else
+        CopyPixel(neighbourhood,4,result,2,channels);
+
+      if (PixelsEqual(neighbourhood,5,neighbourhood,7,channels))
+        CopyPixel(neighbourhood,5,result,3,channels);
+      else
+        CopyPixel(neighbourhood,4,result,3,channels);
+    }
+}
+
+void Epbx2X(const Image *src_image,const Quantum *neighbourhood,Quantum *result,
+  size_t channels)
+{
+#define HelperCond(a,b,c,d,e,f,g) (\
+  PixelsEqual(neighbourhood,a,neighbourhood,b,channels) && (\
+    PixelsEqual(neighbourhood,c,neighbourhood,d,channels) ||\
+    PixelsEqual(neighbourhood,c,neighbourhood,e,channels) ||\
+    PixelsEqual(neighbourhood,a,neighbourhood,f,channels) ||\
+    PixelsEqual(neighbourhood,b,neighbourhood,g,channels)\
+    )\
+  )
+
+  for (unsigned char i=0; i<4; i++)
+    CopyPixel(neighbourhood,4,result,i,channels);
+
+  if (
+    !PixelsEqual(neighbourhood,3,neighbourhood,5,channels) &&
+    !PixelsEqual(neighbourhood,1,neighbourhood,7,channels) &&
+    (
+      PixelsEqual(neighbourhood,4,neighbourhood,3,channels) ||
+      PixelsEqual(neighbourhood,4,neighbourhood,7,channels) ||
+      PixelsEqual(neighbourhood,4,neighbourhood,5,channels) ||
+      PixelsEqual(neighbourhood,4,neighbourhood,1,channels) ||
+      (
+        (
+          !PixelsEqual(neighbourhood,0,neighbourhood,8,channels) ||
+           PixelsEqual(neighbourhood,4,neighbourhood,6,channels) ||
+           PixelsEqual(neighbourhood,3,neighbourhood,2,channels)
+        ) &&
+        (
+          !PixelsEqual(neighbourhood,6,neighbourhood,2,channels) ||
+           PixelsEqual(neighbourhood,4,neighbourhood,0,channels) ||
+           PixelsEqual(neighbourhood,4,neighbourhood,8,channels)
+        )
+      )
+    )
+  )
+    {
+      if (HelperCond(1,3,4,0,8,2,6))
+        Mix2Pixels(neighbourhood,1,3,result,0,channels);
+
+      if (HelperCond(5,1,4,2,6,8,0))
+        Mix2Pixels(neighbourhood,5,1,result,1,channels);
+
+      if (HelperCond(3,7,4,6,2,0,8))
+        Mix2Pixels(neighbourhood,3,7,result,2,channels);
+
+      if (HelperCond(7,5,4,8,0,6,2))
+        Mix2Pixels(neighbourhood,7,5,result,3,channels);
+    }
+
+#undef HelperCond
+}
+
+void Eagle3X(const Image *src_image,const Quantum *neighbourhood,
+  Quantum *result,size_t channels)
+{
+  const int corner_tl =
+    PixelsEqual(neighbourhood,0,neighbourhood,1,channels) &&
+    PixelsEqual(neighbourhood,0,neighbourhood,3,channels);
+
+  const int corner_tr =
+    PixelsEqual(neighbourhood,1,neighbourhood,2,channels) &&
+    PixelsEqual(neighbourhood,2,neighbourhood,5,channels);
+
+  const int corner_bl =
+    PixelsEqual(neighbourhood,3,neighbourhood,6,channels) &&
+    PixelsEqual(neighbourhood,6,neighbourhood,7,channels);
+
+  const int corner_br =
+    PixelsEqual(neighbourhood,5,neighbourhood,7,channels) &&
+    PixelsEqual(neighbourhood,7,neighbourhood,8,channels);
+
+  CopyPixel(neighbourhood,corner_tl ? 0 : 4,result,0,channels);
+
+  if (corner_tl && corner_tr)
+    Mix2Pixels(neighbourhood,0,2,result,1,channels);
+  else
+    CopyPixel(neighbourhood,4,result,1,channels); 
+
+  CopyPixel(neighbourhood,corner_tr ? 1 : 4,result,2,channels);
+
+  if (corner_tl && corner_bl)
+    Mix2Pixels(neighbourhood,0,6,result,3,channels);
+  else
+    CopyPixel(neighbourhood,4,result,3,channels); 
+
+  CopyPixel(neighbourhood,4,result,4,channels);
+
+  if (corner_tr && corner_br)
+    Mix2Pixels(neighbourhood,2,8,result,5,channels);
+  else
+    CopyPixel(neighbourhood,4,result,5,channels); 
+
+  CopyPixel(neighbourhood,corner_bl ? 3 : 4,result,6,channels);
+
+  if (corner_bl && corner_br)
+    Mix2Pixels(neighbourhood,6,8,result,7,channels);
+  else
+    CopyPixel(neighbourhood,4,result,7,channels); 
+
+  CopyPixel(neighbourhood,corner_br ? 5 : 4,result,8,channels);
+}
+
+void Eagle3XB(const Image *src_image,const Quantum *neighbourhood,
+  Quantum *result,size_t channels)
+{
+  const int corner_tl =
+    PixelsEqual(neighbourhood,0,neighbourhood,1,channels) &&
+    PixelsEqual(neighbourhood,0,neighbourhood,3,channels);
+
+  const int corner_tr =
+    PixelsEqual(neighbourhood,1,neighbourhood,2,channels) &&
+    PixelsEqual(neighbourhood,2,neighbourhood,5,channels);
+
+  const int corner_bl =
+    PixelsEqual(neighbourhood,3,neighbourhood,6,channels) &&
+    PixelsEqual(neighbourhood,6,neighbourhood,7,channels);
+
+  const int corner_br =
+    PixelsEqual(neighbourhood,5,neighbourhood,7,channels) &&
+    PixelsEqual(neighbourhood,7,neighbourhood,8,channels);
+
+  CopyPixel(neighbourhood,corner_tl ? 0 : 4,result,0,channels);
+  CopyPixel(neighbourhood,4,result,1,channels); 
+  CopyPixel(neighbourhood,corner_tr ? 1 : 4,result,2,channels);
+  CopyPixel(neighbourhood,4,result,3,channels); 
+  CopyPixel(neighbourhood,4,result,4,channels);
+  CopyPixel(neighbourhood,4,result,5,channels); 
+  CopyPixel(neighbourhood,corner_bl ? 3 : 4,result,6,channels);
+  CopyPixel(neighbourhood,4,result,7,channels); 
+  CopyPixel(neighbourhood,corner_br ? 5 : 4,result,8,channels);
+}
+
+void Scale3X(const Image *src_image,const Quantum *neighbourhood,
+  Quantum *result,size_t channels)
+{
+  if (!PixelsEqual(neighbourhood,1,neighbourhood,7,channels) &&
+      !PixelsEqual(neighbourhood,3,neighbourhood,5,channels))
+    {
+      if (PixelsEqual(neighbourhood,3,neighbourhood,1,channels))
+        CopyPixel(neighbourhood,3,result,0,channels);
+      else
+        CopyPixel(neighbourhood,4,result,0,channels);
+
+      if (
+        (
+          PixelsEqual(neighbourhood,3,neighbourhood,1,channels) &&
+          !PixelsEqual(neighbourhood,4,neighbourhood,2,channels)
+        ) ||
+        (
+          PixelsEqual(neighbourhood,5,neighbourhood,1,channels) &&
+          !PixelsEqual(neighbourhood,4,neighbourhood,0,channels)
+        )
+      )
+        CopyPixel(neighbourhood,1,result,1,channels);
+      else
+        CopyPixel(neighbourhood,4,result,1,channels);
+
+      if (PixelsEqual(neighbourhood,5,neighbourhood,1,channels))
+        CopyPixel(neighbourhood,5,result,2,channels);
+      else
+        CopyPixel(neighbourhood,4,result,2,channels);
+
+      if (
+        (
+          PixelsEqual(neighbourhood,3,neighbourhood,1,channels) &&
+          !PixelsEqual(neighbourhood,4,neighbourhood,6,channels)
+        ) ||
+        (
+          PixelsEqual(neighbourhood,3,neighbourhood,7,channels) &&
+          !PixelsEqual(neighbourhood,4,neighbourhood,0,channels)
+        )
+      )
+        CopyPixel(neighbourhood,3,result,3,channels);
+      else
+        CopyPixel(neighbourhood,4,result,3,channels);
+
+      CopyPixel(neighbourhood,4,result,4,channels);
+
+      if (
+        (
+          PixelsEqual(neighbourhood,5,neighbourhood,1,channels) &&
+          !PixelsEqual(neighbourhood,4,neighbourhood,8,channels)
+        ) ||
+        (
+          PixelsEqual(neighbourhood,5,neighbourhood,7,channels) &&
+          !PixelsEqual(neighbourhood,4,neighbourhood,2,channels)
+        )
+      )
+        CopyPixel(neighbourhood,5,result,5,channels);
+      else      
+        CopyPixel(neighbourhood,4,result,5,channels);
+
+      if (PixelsEqual(neighbourhood,3,neighbourhood,7,channels))
+        CopyPixel(neighbourhood,3,result,6,channels);
+      else 
+        CopyPixel(neighbourhood,4,result,6,channels);
+
+      if (
+        (
+          PixelsEqual(neighbourhood,3,neighbourhood,7,channels) &&
+          !PixelsEqual(neighbourhood,4,neighbourhood,8,channels)
+        ) ||
+        (
+          PixelsEqual(neighbourhood,5,neighbourhood,7,channels) &&
+          !PixelsEqual(neighbourhood,4,neighbourhood,6,channels)
+        )
+      )
+        CopyPixel(neighbourhood,7,result,7,channels);
+      else      
+        CopyPixel(neighbourhood,4,result,7,channels);
+
+      if (PixelsEqual(neighbourhood,5,neighbourhood,7,channels))
+        CopyPixel(neighbourhood,5,result,8,channels);
+      else 
+        CopyPixel(neighbourhood,4,result,8,channels);
+    }
+  else
+    {
+      for (unsigned char i=0; i<9; i++)
+        CopyPixel(neighbourhood,4,result,i,channels);
+    }
+}
+
 /*
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %                                                                             %
@@ -2069,6 +2725,7 @@ MagickExport Image *MagnifyImage(const Image *image,ExceptionInfo *exception)
     *magnify_view;
 
   Image
+    *source_image,
     *magnify_image;
 
   MagickBooleanType
@@ -2080,6 +2737,90 @@ MagickExport Image *MagnifyImage(const Image *image,ExceptionInfo *exception)
   ssize_t
     y;
 
+  const char *
+    algorithm;
+
+  unsigned char
+    magnification,
+    neighbourhood;
+
+  void
+    (*alg_function)(const Image *,const Quantum *,Quantum *,size_t) = NULL;
+
+  algorithm = GetImageOption(image->image_info,"magnify:method");
+
+  if (algorithm == (char *) NULL)
+    algorithm = "scale2x";
+  
+  if (LocaleCompare(algorithm,"scale2x") == 0 ||
+      LocaleCompare(algorithm,"epx") == 0 ||
+      LocaleCompare(algorithm,"scale2") == 0)
+    {
+      alg_function = Scale2X;
+      magnification = 2;
+      neighbourhood = 3;
+    }
+  else if (LocaleCompare(algorithm,"eagle2x") == 0 ||
+      LocaleCompare(algorithm,"eagle2") == 0 ||
+      LocaleCompare(algorithm,"eagle") == 0)
+    {
+      alg_function = Eagle2X;
+      magnification = 2;
+      neighbourhood = 3;
+    }
+  else if (LocaleCompare(algorithm,"scale3x") == 0 ||
+      LocaleCompare(algorithm,"scale3") == 0)
+    {
+      alg_function = Scale3X;
+      magnification = 3;
+      neighbourhood = 3;
+    }
+  else if (LocaleCompare(algorithm,"eagle3x") == 0 ||
+      LocaleCompare(algorithm,"eagle3") == 0)
+    {
+      alg_function = Eagle3X;
+      magnification = 3;
+      neighbourhood = 3;
+    }
+  else if (LocaleCompare(algorithm,"eagle3xb") == 0 ||
+      LocaleCompare(algorithm,"eagle3b") == 0)
+    {
+      alg_function = Eagle3XB;
+      magnification = 3;
+      neighbourhood = 3;
+    }
+  else if (LocaleCompare(algorithm,"epbx") == 0 ||
+      LocaleCompare(algorithm,"epbx2") == 0 ||
+      LocaleCompare(algorithm,"epbx2x") == 0)
+    {
+      alg_function = Epbx2X;
+      magnification = 2;
+      neighbourhood = 3;
+    }
+  else if (LocaleCompare(algorithm,"hqx") == 0 ||
+      LocaleCompare(algorithm,"hq2x") == 0)
+    {
+      alg_function = Hq2X;
+      magnification = 2;
+      neighbourhood = 3;
+    }
+  else if (LocaleCompare(algorithm,"fish") == 0 ||
+      LocaleCompare(algorithm,"fish2") == 0 ||
+      LocaleCompare(algorithm,"fish2x") == 0)
+    {
+      alg_function = Fish2X;
+      magnification = 2;
+      neighbourhood = 3;
+    }
+  else if (LocaleCompare(algorithm,"xbr") == 0 ||
+      LocaleCompare(algorithm,"xbr2") == 0 ||
+      LocaleCompare(algorithm,"xbr2x") == 0)
+    {
+      alg_function = Xbr2X;
+      magnification = 2;
+      neighbourhood = 5;
+    }
+
   /*
     Initialize magnified image attributes.
   */
@@ -2089,22 +2830,40 @@ MagickExport Image *MagnifyImage(const Image *image,ExceptionInfo *exception)
     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
   assert(exception != (ExceptionInfo *) NULL);
   assert(exception->signature == MagickCoreSignature);
-  magnify_image=CloneImage(image,2*image->columns,2*image->rows,MagickTrue,
+
+  /*
+    Make a working copy of the source image and convert it to RGB colorspace.
+  */
+  source_image=CloneImage(image,image->columns,image->rows,MagickTrue,
     exception);
+  OffsetInfo offset;
+  offset.x = 0;
+  offset.y = 0;
+  RectangleInfo rectangle;
+  rectangle.x = 0;
+  rectangle.y = 0;
+  rectangle.width = image->columns;
+  rectangle.height = image->rows;
+  CopyImagePixels(source_image,image,&rectangle,&offset,exception);
+  SetImageColorspace(source_image,RGBColorspace,exception);
+
+  magnify_image=CloneImage(source_image,magnification*source_image->columns,
+    magnification*source_image->rows,MagickTrue,exception);
   if (magnify_image == (Image *) NULL)
     return((Image *) NULL);
+
   /*
-    Magnify image.
+    Magnify the image.
   */
   status=MagickTrue;
   progress=0;
-  image_view=AcquireVirtualCacheView(image,exception);
+  image_view=AcquireVirtualCacheView(source_image,exception);
   magnify_view=AcquireAuthenticCacheView(magnify_image,exception);
 #if defined(MAGICKCORE_OPENMP_SUPPORT)
   #pragma omp parallel for schedule(static) shared(progress,status) \
-    magick_number_threads(image,magnify_image,image->rows,1)
+    magick_number_threads(source_image,magnify_image,source_image->rows,1)
 #endif
-  for (y=0; y < (ssize_t) image->rows; y++)
+  for (y=0; y < (ssize_t) source_image->rows; y++)
   {
     register Quantum
       *magick_restrict q;
@@ -2112,98 +2871,50 @@ MagickExport Image *MagnifyImage(const Image *image,ExceptionInfo *exception)
     register ssize_t
       x;
 
+    Quantum r[128]; // to hold result pixels
+
     if (status == MagickFalse)
       continue;
-    q=QueueCacheViewAuthenticPixels(magnify_view,0,2*y,magnify_image->columns,2,
-      exception);
+    q=QueueCacheViewAuthenticPixels(magnify_view,0,magnification*y,
+      magnify_image->columns,magnification,exception);
     if (q == (Quantum *) NULL)
       {
         status=MagickFalse;
         continue;
       }
+
     /*
       Magnify this row of pixels.
     */
-    for (x=0; x < (ssize_t) image->columns; x++)
+    for (x=0; x < (ssize_t) source_image->columns; x++)
     {
-      MagickRealType
-        intensity[9];
-
       register const Quantum
         *magick_restrict p;
 
-      register Quantum
-        *magick_restrict r;
+      ssize_t
+        channels;
 
       register ssize_t
-        i;
+        i, j;
 
-      size_t
-        channels;
+      p=GetCacheViewVirtualPixels(image_view,x-neighbourhood/2,
+        y-neighbourhood/2,neighbourhood,neighbourhood,exception);
+      
+      channels = GetPixelChannels(source_image);
 
-      p=GetCacheViewVirtualPixels(image_view,x-1,y-1,3,3,exception);
-      if (p == (const Quantum *) NULL)
-        {
-          status=MagickFalse;
-          continue;
-        }
-      channels=GetPixelChannels(image);
-      for (i=0; i < 9; i++)
-        intensity[i]=GetPixelIntensity(image,p+i*channels);
-      r=q;
-      if ((fabs(intensity[1]-intensity[7]) < MagickEpsilon) ||
-          (fabs(intensity[3]-intensity[5]) < MagickEpsilon))
-        {
-          /*
-            Clone center pixel.
-          */
-          for (i=0; i < (ssize_t) channels; i++)
-            r[i]=p[4*channels+i];
-          r+=GetPixelChannels(magnify_image);
-          for (i=0; i < (ssize_t) channels; i++)
-            r[i]=p[4*channels+i];
-          r+=GetPixelChannels(magnify_image)*(magnify_image->columns-1);
-          for (i=0; i < (ssize_t) channels; i++)
-            r[i]=p[4*channels+i];
-          r+=GetPixelChannels(magnify_image);
-          for (i=0; i < (ssize_t) channels; i++)
-            r[i]=p[4*channels+i];
-        }
-      else
-        {
-          /*
-            Selectively clone pixel.
-          */
-          if (fabs(intensity[1]-intensity[3]) < MagickEpsilon)
-            for (i=0; i < (ssize_t) channels; i++)
-              r[i]=p[3*channels+i];
-          else
-            for (i=0; i < (ssize_t) channels; i++)
-              r[i]=p[4*channels+i];
-          r+=GetPixelChannels(magnify_image);
-          if (fabs(intensity[1]-intensity[5]) < MagickEpsilon)
-            for (i=0; i < (ssize_t) channels; i++)
-              r[i]=p[5*channels+i];
-          else
-            for (i=0; i < (ssize_t) channels; i++)
-              r[i]=p[4*channels+i];
-          r+=GetPixelChannels(magnify_image)*(magnify_image->columns-1);
-          if (fabs(intensity[3]-intensity[7]) < MagickEpsilon)
-            for (i=0; i < (ssize_t) channels; i++)
-              r[i]=p[3*channels+i];
-          else
-            for (i=0; i < (ssize_t) channels; i++)
-              r[i]=p[4*channels+i];
-          r+=GetPixelChannels(magnify_image);
-          if (fabs(intensity[5]-intensity[7]) < MagickEpsilon)
-            for (i=0; i < (ssize_t) channels; i++)
-              r[i]=p[5*channels+i];
-          else
-            for (i=0; i < (ssize_t) channels; i++)
-              r[i]=p[4*channels+i];
-        }
-      q+=2*GetPixelChannels(magnify_image);
+      alg_function(source_image,p,r,channels);
+
+      /*
+        Copy the result pixels into the final image.
+      */
+      for (j=0; j < magnification; j++)
+        for (i=0; i < (ssize_t) channels*magnification; i++)
+          q[j*channels*magnify_image->columns+i]=
+            r[j*magnification*channels+i];
+
+      q+=magnification*GetPixelChannels(magnify_image);
     }
+
     if (SyncCacheViewAuthenticPixels(magnify_view,exception) == MagickFalse)
       status=MagickFalse;
     if (image->progress_monitor != (MagickProgressMonitor) NULL)
@@ -2223,7 +2934,10 @@ MagickExport Image *MagnifyImage(const Image *image,ExceptionInfo *exception)
   magnify_view=DestroyCacheView(magnify_view);
   image_view=DestroyCacheView(image_view);
   if (status == MagickFalse)
+  {
     magnify_image=DestroyImage(magnify_image);
+    source_image=DestroyImage(source_image);
+  }
   return(magnify_image);
 }
 \f