diff --git a/src/conio/GNUmakefile b/src/conio/GNUmakefile
index 52aece4de9a9bdce7af0cdd265d27669b3c7b67a..04a86f256d5a317b0527b605756e5cbffb301081 100644
--- a/src/conio/GNUmakefile
+++ b/src/conio/GNUmakefile
@@ -15,7 +15,9 @@ ifdef NO_X
  CFLAGS	+=	-DNO_X
 else
  OBJS	+=	$(MTOBJODIR)$(DIRSEP)x_events$(OFILE) \
-			$(MTOBJODIR)$(DIRSEP)x_cio$(OFILE)
+			$(MTOBJODIR)$(DIRSEP)x_cio$(OFILE) \
+			$(MTOBJODIR)$(DIRSEP)scale$(OFILE) \
+			$(MTOBJODIR)$(DIRSEP)xbr$(OFILE)
  NEED_BITMAP := 1
 endif
 
diff --git a/src/conio/hqx.c b/src/conio/hqx.c
new file mode 100644
index 0000000000000000000000000000000000000000..2d7fd4db5144466d21d0ca7f703a78fe775f74c8
--- /dev/null
+++ b/src/conio/hqx.c
@@ -0,0 +1,414 @@
+/*
+ * Copyright (c) 2014 Clément Bœsch
+ *
+ * This file is part of FFmpeg.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/**
+ * @file
+ * hqx magnification filters (hq2x, hq3x, hq4x)
+ *
+ * Originally designed by Maxim Stephin.
+ *
+ * @see http://en.wikipedia.org/wiki/Hqx
+ * @see http://web.archive.org/web/20131114143602/http://www.hiend3d.com/hq3x.html
+ * @see http://blog.pkh.me/p/19-butchering-hqx-scaling-filters.html
+ */
+
+#include <inttypes.h>
+
+#if 0
+/* (c1*w1 + c2*w2) >> s */
+inline __attribute__((always_inline)) uint32_t interp_2px(uint32_t c1, int w1, uint32_t c2, int w2, int s)
+{
+    return (((((c1 & 0xff00ff00) >> 8) * w1 + ((c2 & 0xff00ff00) >> 8) * w2) << (8 - s)) & 0xff00ff00) |
+           (((((c1 & 0x00ff00ff)     ) * w1 + ((c2 & 0x00ff00ff)     ) * w2) >>      s ) & 0x00ff00ff);
+}
+
+/* (c1*w1 + c2*w2 + c3*w3) >> s */
+inline __attribute__((always_inline)) uint32_t interp_3px(uint32_t c1, int w1, uint32_t c2, int w2, uint32_t c3, int w3, int s)
+{
+    return (((((c1 & 0xff00ff00) >> 8) * w1 + ((c2 & 0xff00ff00) >> 8) * w2 + ((c3 & 0xff00ff00) >> 8) * w3) << (8 - s)) & 0xff00ff00) |
+           (((((c1 & 0x00ff00ff)     ) * w1 + ((c2 & 0x00ff00ff)     ) * w2 + ((c3 & 0x00ff00ff)     ) * w3) >>      s ) & 0x00ff00ff);
+}
+#else
+/* (c1*w1 + c2*w2) >> s */
+inline __attribute__((always_inline)) uint32_t interp_2px(uint32_t c1, int w1, uint32_t c2, int w2, int s)
+{
+	return w2 > w1 ? c2 : c1;
+}
+
+/* (c1*w1 + c2*w2 + c3*w3) >> s */
+inline __attribute__((always_inline)) uint32_t interp_3px(uint32_t c1, int w1, uint32_t c2, int w2, uint32_t c3, int w3, int s)
+{
+	if (w3 > w2 && w3 > w1)
+		return c3;
+	if (w2 > w1)
+		return c2;
+	return c1;
+}
+#endif
+
+/* m is the mask of diff with the center pixel that matters in the pattern, and
+ * r is the expected result (bit set to 1 if there is difference with the
+ * center, 0 otherwise) */
+#define P(m, r) ((k_shuffled & (m)) == (r))
+
+/* adjust 012345678 to 01235678: the mask doesn't contain the (null) diff
+ * between the center/current pixel and itself */
+#define DROP4(z) ((z) > 4 ? (z)-1 : (z))
+
+/* shuffle the input mask: move bit n (4-adjusted) to position stored in p<n> */
+#define SHF(x, rot, n) (((x) >> ((rot) ? 7-DROP4(n) : DROP4(n)) & 1) << DROP4(p##n))
+
+/* used to check if there is YUV difference between 2 pixels */
+#define WDIFF(c1, c2) (c1 != c2)
+
+/* bootstrap template for every interpolation code. It defines the shuffled
+ * masks and surrounding pixels. The rot flag is used to indicate if it's a
+ * rotation; its basic effect is to shuffle k using p8..p0 instead of p0..p8 */
+#define INTERP_BOOTSTRAP(rot)                                           \
+    const int k_shuffled = SHF(k,rot,0) | SHF(k,rot,1) | SHF(k,rot,2)   \
+                         | SHF(k,rot,3) |       0      | SHF(k,rot,5)   \
+                         | SHF(k,rot,6) | SHF(k,rot,7) | SHF(k,rot,8);  \
+                                                                        \
+    const uint32_t w0 = w[p0], w1 = w[p1],                              \
+                   w3 = w[p3], w4 = w[p4], w5 = w[p5],                  \
+                               w7 = w[p7]
+
+/* Assuming p0..p8 is mapped to pixels 0..8, this function interpolates the
+ * top-left pixel in the total of the 2x2 pixels to interpolates. The function
+ * is also used for the 3 other pixels */
+inline __attribute__((always_inline)) uint32_t hq2x_interp_1x1(int k,
+                                                 const uint32_t *w,
+                                                 int p0, int p1, int p2,
+                                                 int p3, int p4, int p5,
+                                                 int p6, int p7, int p8)
+{
+    INTERP_BOOTSTRAP(0);
+
+    if ((P(0xbf,0x37) || P(0xdb,0x13)) && WDIFF(w1, w5))
+        return interp_2px(w4, 3, w3, 1, 2);
+    if ((P(0xdb,0x49) || P(0xef,0x6d)) && WDIFF(w7, w3))
+        return interp_2px(w4, 3, w1, 1, 2);
+    if ((P(0x0b,0x0b) || P(0xfe,0x4a) || P(0xfe,0x1a)) && WDIFF(w3, w1))
+        return w4;
+    if ((P(0x6f,0x2a) || P(0x5b,0x0a) || P(0xbf,0x3a) || P(0xdf,0x5a) ||
+         P(0x9f,0x8a) || P(0xcf,0x8a) || P(0xef,0x4e) || P(0x3f,0x0e) ||
+         P(0xfb,0x5a) || P(0xbb,0x8a) || P(0x7f,0x5a) || P(0xaf,0x8a) ||
+         P(0xeb,0x8a)) && WDIFF(w3, w1))
+        return interp_2px(w4, 3, w0, 1, 2);
+    if (P(0x0b,0x08))
+        return interp_3px(w4, 2, w0, 1, w1, 1, 2);
+    if (P(0x0b,0x02))
+        return interp_3px(w4, 2, w0, 1, w3, 1, 2);
+    if (P(0x2f,0x2f))
+        return interp_3px(w4, 14, w3, 1, w1, 1, 4);
+    if (P(0xbf,0x37) || P(0xdb,0x13))
+        return interp_3px(w4, 5, w1, 2, w3, 1, 3);
+    if (P(0xdb,0x49) || P(0xef,0x6d))
+        return interp_3px(w4, 5, w3, 2, w1, 1, 3);
+    if (P(0x1b,0x03) || P(0x4f,0x43) || P(0x8b,0x83) || P(0x6b,0x43))
+        return interp_2px(w4, 3, w3, 1, 2);
+    if (P(0x4b,0x09) || P(0x8b,0x89) || P(0x1f,0x19) || P(0x3b,0x19))
+        return interp_2px(w4, 3, w1, 1, 2);
+    if (P(0x7e,0x2a) || P(0xef,0xab) || P(0xbf,0x8f) || P(0x7e,0x0e))
+        return interp_3px(w4, 2, w3, 3, w1, 3, 3);
+    if (P(0xfb,0x6a) || P(0x6f,0x6e) || P(0x3f,0x3e) || P(0xfb,0xfa) ||
+        P(0xdf,0xde) || P(0xdf,0x1e))
+        return interp_2px(w4, 3, w0, 1, 2);
+    if (P(0x0a,0x00) || P(0x4f,0x4b) || P(0x9f,0x1b) || P(0x2f,0x0b) ||
+        P(0xbe,0x0a) || P(0xee,0x0a) || P(0x7e,0x0a) || P(0xeb,0x4b) ||
+        P(0x3b,0x1b))
+        return interp_3px(w4, 2, w3, 1, w1, 1, 2);
+    return interp_3px(w4, 6, w3, 1, w1, 1, 3);
+}
+
+/* Assuming p0..p8 is mapped to pixels 0..8, this function interpolates the
+ * top-left and top-center pixel in the total of the 3x3 pixels to
+ * interpolates. The function is also used for the 3 other couples of pixels
+ * defining the outline. The center pixel is not defined through this function,
+ * since it's just the same as the original value. */
+inline __attribute__((always_inline)) void hq3x_interp_2x1(uint32_t *dst, int dst_linesize,
+                                             int k,
+                                             const uint32_t *w,
+                                             int pos00, int pos01,
+                                             int p0, int p1, int p2,
+                                             int p3, int p4, int p5,
+                                             int p6, int p7, int p8,
+                                             int rotate)
+{
+    INTERP_BOOTSTRAP(rotate);
+
+    uint32_t *dst00 = &dst[dst_linesize*(pos00>>1) + (pos00&1)];
+    uint32_t *dst01 = &dst[dst_linesize*(pos01>>1) + (pos01&1)];
+
+    if ((P(0xdb,0x49) || P(0xef,0x6d)) && WDIFF(w7, w3))
+        *dst00 = interp_2px(w4, 3, w1, 1, 2);
+    else if ((P(0xbf,0x37) || P(0xdb,0x13)) && WDIFF(w1, w5))
+        *dst00 = interp_2px(w4, 3, w3, 1, 2);
+    else if ((P(0x0b,0x0b) || P(0xfe,0x4a) || P(0xfe,0x1a)) && WDIFF(w3, w1))
+        *dst00 = w4;
+    else if ((P(0x6f,0x2a) || P(0x5b,0x0a) || P(0xbf,0x3a) || P(0xdf,0x5a) ||
+              P(0x9f,0x8a) || P(0xcf,0x8a) || P(0xef,0x4e) || P(0x3f,0x0e) ||
+              P(0xfb,0x5a) || P(0xbb,0x8a) || P(0x7f,0x5a) || P(0xaf,0x8a) ||
+              P(0xeb,0x8a)) && WDIFF(w3, w1))
+        *dst00 = interp_2px(w4, 3, w0, 1, 2);
+    else if (P(0x4b,0x09) || P(0x8b,0x89) || P(0x1f,0x19) || P(0x3b,0x19))
+        *dst00 = interp_2px(w4, 3, w1, 1, 2);
+    else if (P(0x1b,0x03) || P(0x4f,0x43) || P(0x8b,0x83) || P(0x6b,0x43))
+        *dst00 = interp_2px(w4, 3, w3, 1, 2);
+    else if (P(0x7e,0x2a) || P(0xef,0xab) || P(0xbf,0x8f) || P(0x7e,0x0e))
+        *dst00 = interp_2px(w3, 1, w1, 1, 1);
+    else if (P(0x4f,0x4b) || P(0x9f,0x1b) || P(0x2f,0x0b) || P(0xbe,0x0a) ||
+             P(0xee,0x0a) || P(0x7e,0x0a) || P(0xeb,0x4b) || P(0x3b,0x1b))
+        *dst00 = interp_3px(w4, 2, w3, 7, w1, 7, 4);
+    else if (P(0x0b,0x08) || P(0xf9,0x68) || P(0xf3,0x62) || P(0x6d,0x6c) ||
+             P(0x67,0x66) || P(0x3d,0x3c) || P(0x37,0x36) || P(0xf9,0xf8) ||
+             P(0xdd,0xdc) || P(0xf3,0xf2) || P(0xd7,0xd6) || P(0xdd,0x1c) ||
+             P(0xd7,0x16) || P(0x0b,0x02))
+        *dst00 = interp_2px(w4, 3, w0, 1, 2);
+    else
+        *dst00 = interp_3px(w4, 2, w3, 1, w1, 1, 2);
+
+    if ((P(0xfe,0xde) || P(0x9e,0x16) || P(0xda,0x12) || P(0x17,0x16) ||
+         P(0x5b,0x12) || P(0xbb,0x12)) && WDIFF(w1, w5))
+        *dst01 = w4;
+    else if ((P(0x0f,0x0b) || P(0x5e,0x0a) || P(0xfb,0x7b) || P(0x3b,0x0b) ||
+              P(0xbe,0x0a) || P(0x7a,0x0a)) && WDIFF(w3, w1))
+        *dst01 = w4;
+    else if (P(0xbf,0x8f) || P(0x7e,0x0e) || P(0xbf,0x37) || P(0xdb,0x13))
+        *dst01 = interp_2px(w1, 3, w4, 1, 2);
+    else if (P(0x02,0x00) || P(0x7c,0x28) || P(0xed,0xa9) || P(0xf5,0xb4) ||
+             P(0xd9,0x90))
+        *dst01 = interp_2px(w4, 3, w1, 1, 2);
+    else if (P(0x4f,0x4b) || P(0xfb,0x7b) || P(0xfe,0x7e) || P(0x9f,0x1b) ||
+             P(0x2f,0x0b) || P(0xbe,0x0a) || P(0x7e,0x0a) || P(0xfb,0x4b) ||
+             P(0xfb,0xdb) || P(0xfe,0xde) || P(0xfe,0x56) || P(0x57,0x56) ||
+             P(0x97,0x16) || P(0x3f,0x1e) || P(0xdb,0x12) || P(0xbb,0x12))
+        *dst01 = interp_2px(w4, 7, w1, 1, 3);
+    else
+        *dst01 = w4;
+}
+
+/* Assuming p0..p8 is mapped to pixels 0..8, this function interpolates the
+ * top-left block of 2x2 pixels in the total of the 4x4 pixels (or 4 blocks) to
+ * interpolates. The function is also used for the 3 other blocks of 2x2
+ * pixels. */
+inline __attribute__((always_inline)) void hq4x_interp_2x2(uint32_t *dst, int dst_linesize,
+                                             int k,
+                                             const uint32_t *w,
+                                             int pos00, int pos01,
+                                             int pos10, int pos11,
+                                             int p0, int p1, int p2,
+                                             int p3, int p4, int p5,
+                                             int p6, int p7, int p8)
+{
+    INTERP_BOOTSTRAP(0);
+
+    uint32_t *dst00 = &dst[dst_linesize*(pos00>>1) + (pos00&1)];
+    uint32_t *dst01 = &dst[dst_linesize*(pos01>>1) + (pos01&1)];
+    uint32_t *dst10 = &dst[dst_linesize*(pos10>>1) + (pos10&1)];
+    uint32_t *dst11 = &dst[dst_linesize*(pos11>>1) + (pos11&1)];
+
+    const int cond00 = (P(0xbf,0x37) || P(0xdb,0x13)) && WDIFF(w1, w5);
+    const int cond01 = (P(0xdb,0x49) || P(0xef,0x6d)) && WDIFF(w7, w3);
+    const int cond02 = (P(0x6f,0x2a) || P(0x5b,0x0a) || P(0xbf,0x3a) ||
+                        P(0xdf,0x5a) || P(0x9f,0x8a) || P(0xcf,0x8a) ||
+                        P(0xef,0x4e) || P(0x3f,0x0e) || P(0xfb,0x5a) ||
+                        P(0xbb,0x8a) || P(0x7f,0x5a) || P(0xaf,0x8a) ||
+                        P(0xeb,0x8a)) && WDIFF(w3, w1);
+    const int cond03 = P(0xdb,0x49) || P(0xef,0x6d);
+    const int cond04 = P(0xbf,0x37) || P(0xdb,0x13);
+    const int cond05 = P(0x1b,0x03) || P(0x4f,0x43) || P(0x8b,0x83) ||
+                       P(0x6b,0x43);
+    const int cond06 = P(0x4b,0x09) || P(0x8b,0x89) || P(0x1f,0x19) ||
+                       P(0x3b,0x19);
+    const int cond07 = P(0x0b,0x08) || P(0xf9,0x68) || P(0xf3,0x62) ||
+                       P(0x6d,0x6c) || P(0x67,0x66) || P(0x3d,0x3c) ||
+                       P(0x37,0x36) || P(0xf9,0xf8) || P(0xdd,0xdc) ||
+                       P(0xf3,0xf2) || P(0xd7,0xd6) || P(0xdd,0x1c) ||
+                       P(0xd7,0x16) || P(0x0b,0x02);
+    const int cond08 = (P(0x0f,0x0b) || P(0x2b,0x0b) || P(0xfe,0x4a) ||
+                        P(0xfe,0x1a)) && WDIFF(w3, w1);
+    const int cond09 = P(0x2f,0x2f);
+    const int cond10 = P(0x0a,0x00);
+    const int cond11 = P(0x0b,0x09);
+    const int cond12 = P(0x7e,0x2a) || P(0xef,0xab);
+    const int cond13 = P(0xbf,0x8f) || P(0x7e,0x0e);
+    const int cond14 = P(0x4f,0x4b) || P(0x9f,0x1b) || P(0x2f,0x0b) ||
+                       P(0xbe,0x0a) || P(0xee,0x0a) || P(0x7e,0x0a) ||
+                       P(0xeb,0x4b) || P(0x3b,0x1b);
+    const int cond15 = P(0x0b,0x03);
+
+    if (cond00)
+        *dst00 = interp_2px(w4, 5, w3, 3, 3);
+    else if (cond01)
+        *dst00 = interp_2px(w4, 5, w1, 3, 3);
+    else if ((P(0x0b,0x0b) || P(0xfe,0x4a) || P(0xfe,0x1a)) && WDIFF(w3, w1))
+        *dst00 = w4;
+    else if (cond02)
+        *dst00 = interp_2px(w4, 5, w0, 3, 3);
+    else if (cond03)
+        *dst00 = interp_2px(w4, 3, w3, 1, 2);
+    else if (cond04)
+        *dst00 = interp_2px(w4, 3, w1, 1, 2);
+    else if (cond05)
+        *dst00 = interp_2px(w4, 5, w3, 3, 3);
+    else if (cond06)
+        *dst00 = interp_2px(w4, 5, w1, 3, 3);
+    else if (P(0x0f,0x0b) || P(0x5e,0x0a) || P(0x2b,0x0b) || P(0xbe,0x0a) ||
+             P(0x7a,0x0a) || P(0xee,0x0a))
+        *dst00 = interp_2px(w1, 1, w3, 1, 1);
+    else if (cond07)
+        *dst00 = interp_2px(w4, 5, w0, 3, 3);
+    else
+        *dst00 = interp_3px(w4, 2, w1, 1, w3, 1, 2);
+
+    if (cond00)
+        *dst01 = interp_2px(w4, 7, w3, 1, 3);
+    else if (cond08)
+        *dst01 = w4;
+    else if (cond02)
+        *dst01 = interp_2px(w4, 3, w0, 1, 2);
+    else if (cond09)
+        *dst01 = w4;
+    else if (cond10)
+        *dst01 = interp_3px(w4, 5, w1, 2, w3, 1, 3);
+    else if (P(0x0b,0x08))
+        *dst01 = interp_3px(w4, 5, w1, 2, w0, 1, 3);
+    else if (cond11)
+        *dst01 = interp_2px(w4, 5, w1, 3, 3);
+    else if (cond04)
+        *dst01 = interp_2px(w1, 3, w4, 1, 2);
+    else if (cond12)
+        *dst01 = interp_3px(w1, 2, w4, 1, w3, 1, 2);
+    else if (cond13)
+        *dst01 = interp_2px(w1, 5, w3, 3, 3);
+    else if (cond05)
+        *dst01 = interp_2px(w4, 7, w3, 1, 3);
+    else if (P(0xf3,0x62) || P(0x67,0x66) || P(0x37,0x36) || P(0xf3,0xf2) ||
+             P(0xd7,0xd6) || P(0xd7,0x16) || P(0x0b,0x02))
+        *dst01 = interp_2px(w4, 3, w0, 1, 2);
+    else if (cond14)
+        *dst01 = interp_2px(w1, 1, w4, 1, 1);
+    else
+        *dst01 = interp_2px(w4, 3, w1, 1, 2);
+
+    if (cond01)
+        *dst10 = interp_2px(w4, 7, w1, 1, 3);
+    else if (cond08)
+        *dst10 = w4;
+    else if (cond02)
+        *dst10 = interp_2px(w4, 3, w0, 1, 2);
+    else if (cond09)
+        *dst10 = w4;
+    else if (cond10)
+        *dst10 = interp_3px(w4, 5, w3, 2, w1, 1, 3);
+    else if (P(0x0b,0x02))
+        *dst10 = interp_3px(w4, 5, w3, 2, w0, 1, 3);
+    else if (cond15)
+        *dst10 = interp_2px(w4, 5, w3, 3, 3);
+    else if (cond03)
+        *dst10 = interp_2px(w3, 3, w4, 1, 2);
+    else if (cond13)
+        *dst10 = interp_3px(w3, 2, w4, 1, w1, 1, 2);
+    else if (cond12)
+        *dst10 = interp_2px(w3, 5, w1, 3, 3);
+    else if (cond06)
+        *dst10 = interp_2px(w4, 7, w1, 1, 3);
+    else if (P(0x0b,0x08) || P(0xf9,0x68) || P(0x6d,0x6c) || P(0x3d,0x3c) ||
+             P(0xf9,0xf8) || P(0xdd,0xdc) || P(0xdd,0x1c))
+        *dst10 = interp_2px(w4, 3, w0, 1, 2);
+    else if (cond14)
+        *dst10 = interp_2px(w3, 1, w4, 1, 1);
+    else
+        *dst10 = interp_2px(w4, 3, w3, 1, 2);
+
+    if ((P(0x7f,0x2b) || P(0xef,0xab) || P(0xbf,0x8f) || P(0x7f,0x0f)) &&
+         WDIFF(w3, w1))
+        *dst11 = w4;
+    else if (cond02)
+        *dst11 = interp_2px(w4, 7, w0, 1, 3);
+    else if (cond15)
+        *dst11 = interp_2px(w4, 7, w3, 1, 3);
+    else if (cond11)
+        *dst11 = interp_2px(w4, 7, w1, 1, 3);
+    else if (P(0x0a,0x00) || P(0x7e,0x2a) || P(0xef,0xab) || P(0xbf,0x8f) ||
+             P(0x7e,0x0e))
+        *dst11 = interp_3px(w4, 6, w3, 1, w1, 1, 3);
+    else if (cond07)
+        *dst11 = interp_2px(w4, 7, w0, 1, 3);
+    else
+        *dst11 = w4;
+}
+
+void hqx_filter(uint32_t* src, uint32_t* dst, int width, int height, int n)
+{
+    int x, y;
+    const int dst32_linesize = width * n;
+    const int src32_linesize = width;
+    uint32_t *src32, *dst32;
+
+    src32 = src;
+    dst32 = dst;
+    for (y = 0; y < height; y++) {
+        const int prevline = y > 0          ? -src32_linesize : 0;
+        const int nextline = y < height - 1 ?  src32_linesize : 0;
+
+        for (x = 0; x < width; x++) {
+            const int prevcol = x > 0        ? -1 : 0;
+            const int nextcol = x < width -1 ?  1 : 0;
+            const uint32_t w[3*3] = {
+                src32[prevcol + prevline], src32[prevline], src32[prevline + nextcol],
+                src32[prevcol           ], src32[       0], src32[           nextcol],
+                src32[prevcol + nextline], src32[nextline], src32[nextline + nextcol]
+            };
+            const uint32_t yuv1 = w[4];
+            const int pattern = (w[4] != w[0] ? 1 : 0)
+                              | (w[4] != w[1] ? 1 : 0) << 1
+                              | (w[4] != w[2] ? 1 : 0) << 2
+                              | (w[4] != w[3] ? 1 : 0) << 3
+                              | (w[4] != w[5] ? 1 : 0) << 4
+                              | (w[4] != w[6] ? 1 : 0) << 5
+                              | (w[4] != w[7] ? 1 : 0) << 6
+                              | (w[4] != w[8] ? 1 : 0) << 7;
+
+            if (n == 2) {
+                dst32[dst32_linesize*0 + 0] = hq2x_interp_1x1(pattern, w, 0,1,2,3,4,5,6,7,8);  // 00
+                dst32[dst32_linesize*0 + 1] = hq2x_interp_1x1(pattern, w, 2,1,0,5,4,3,8,7,6);  // 01 (vert mirrored)
+                dst32[dst32_linesize*1 + 0] = hq2x_interp_1x1(pattern, w, 6,7,8,3,4,5,0,1,2);  // 10 (horiz mirrored)
+                dst32[dst32_linesize*1 + 1] = hq2x_interp_1x1(pattern, w, 8,7,6,5,4,3,2,1,0);  // 11 (center mirrored)
+            } else if (n == 3) {
+                hq3x_interp_2x1(dst32,                        dst32_linesize, pattern, w, 0,1, 0,1,2,3,4,5,6,7,8, 0);  // 00 01
+                hq3x_interp_2x1(dst32 + 1,                    dst32_linesize, pattern, w, 1,3, 2,5,8,1,4,7,0,3,6, 1);  // 02 12 (rotated to the right)
+                hq3x_interp_2x1(dst32 + 1*dst32_linesize,     dst32_linesize, pattern, w, 2,0, 6,3,0,7,4,1,8,5,2, 1);  // 20 10 (rotated to the left)
+                hq3x_interp_2x1(dst32 + 1*dst32_linesize + 1, dst32_linesize, pattern, w, 3,2, 8,7,6,5,4,3,2,1,0, 0);  // 22 21 (center mirrored)
+                dst32[dst32_linesize + 1] = w[4];                                                                           // 11
+            } else if (n == 4) {
+                hq4x_interp_2x2(dst32,                        dst32_linesize, pattern, w, 0,1,2,3, 0,1,2,3,4,5,6,7,8); // 00 01 10 11
+                hq4x_interp_2x2(dst32 + 2,                    dst32_linesize, pattern, w, 1,0,3,2, 2,1,0,5,4,3,8,7,6); // 02 03 12 13 (vert mirrored)
+                hq4x_interp_2x2(dst32 + 2*dst32_linesize,     dst32_linesize, pattern, w, 2,3,0,1, 6,7,8,3,4,5,0,1,2); // 20 21 30 31 (horiz mirrored)
+                hq4x_interp_2x2(dst32 + 2*dst32_linesize + 2, dst32_linesize, pattern, w, 3,2,1,0, 8,7,6,5,4,3,2,1,0); // 22 23 32 33 (center mirrored)
+            } else {
+		return;
+            }
+
+            src32 += 1;
+            dst32 += n;
+        }
+	dst32 += dst32_linesize * (n - 1);
+    }
+}
diff --git a/src/conio/hqx.h b/src/conio/hqx.h
new file mode 100644
index 0000000000000000000000000000000000000000..c9f347e05ca6090d1f32a6fd091fe005ef71fec2
--- /dev/null
+++ b/src/conio/hqx.h
@@ -0,0 +1,11 @@
+#include <inttypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void hqx_filter(uint32_t* src, uint32_t* dst, int height, int width, int n);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/conio/objects.mk b/src/conio/objects.mk
index 0ce6f3c87b4fde32c10a5033ce186a3618d7d619..b5a513acac5a49d9bc5182c551a263db2de1eeb1 100644
--- a/src/conio/objects.mk
+++ b/src/conio/objects.mk
@@ -4,5 +4,6 @@ OBJS	=	$(MTOBJODIR)$(DIRSEP)ansi_cio$(OFILE)\
 			$(MTOBJODIR)$(DIRSEP)vidmodes$(OFILE)\
 			$(MTOBJODIR)$(DIRSEP)allfonts$(OFILE)\
 			$(MTOBJODIR)$(DIRSEP)mouse$(OFILE)\
+			$(MTOBJODIR)$(DIRSEP)scale$(OFILE)\
 			$(MTOBJODIR)$(DIRSEP)syncicon64$(OFILE)\
 			$(MTOBJODIR)$(DIRSEP)utf8_codepages$(OFILE)
diff --git a/src/conio/scale.c b/src/conio/scale.c
new file mode 100644
index 0000000000000000000000000000000000000000..ff9fc37c5870bc989f460aae82b49543c867fc4a
--- /dev/null
+++ b/src/conio/scale.c
@@ -0,0 +1,341 @@
+#include "scale.h"
+#include "xbr.h"
+
+static void pointy_scale3(uint32_t* src, uint32_t* dest, int width, int height);
+static void pointy_scale5(uint32_t* src, uint32_t* dest, int width, int height);
+
+uint32_t*
+do_scale(struct rectlist* rect, uint32_t **target, size_t *targetsz, int* xscale, int* yscale, int* w, int* h)
+{
+	int pointy5 = 0;
+	int pointy3 = 0;
+	int xbr2 = 0;
+	int xbr4 = 0;
+	int newscale = 1;
+	int total_scaling = 1;
+	static uint32_t *tmptarget = NULL;
+	static size_t tmptargetsz = 0;
+	uint32_t *ctarget;
+	uint32_t *csrc = rect->data;
+	uint32_t* nt;
+	int swidth = rect->rect.width;
+	int sheight = rect->rect.height;
+
+	*w = rect->rect.width;
+	*h = rect->rect.height;
+	// As a first pass, only do equal integer scaling
+	if (*xscale != *yscale)
+		return rect->data;
+	switch (*xscale) {
+		case 1:
+			return rect->data;
+		case 2:
+			xbr2 = 1;
+			break;
+		case 3:
+			pointy3 = 1;
+			break;
+		case 4:
+			xbr4 = 1;
+			break;
+		case 5:
+			pointy5 = 1;
+			break;
+		case 6:
+			pointy3 = 1;
+			xbr2 = 1;
+			break;
+		default:
+			total_scaling = *xscale;
+			*xscale = 1;
+			while (total_scaling > 1 && ((total_scaling % 5) == 0)) {
+				pointy5++;
+				total_scaling /= 5;
+				*xscale *= 5;
+			}
+			while (total_scaling > 1 && ((total_scaling % 3) == 0)) {
+				pointy3++;
+				total_scaling /= 3;
+				*xscale *= 3;
+			}
+			while (total_scaling > 1 && ((total_scaling % 4) == 0)) {
+				xbr4++;
+				total_scaling /= 4;
+				*xscale *= 4;
+			}
+			while (total_scaling > 1 && ((total_scaling % 2) == 0)) {
+				xbr2++;
+				total_scaling /= 2;
+				*xscale *= 2;
+			}
+			if (*xscale == 1)
+				return rect->data;
+			break;
+	}
+
+	// Now make sure target is big enough...
+	size_t needsz = rect->rect.width * rect->rect.height * (*xscale) * (*xscale) * sizeof(uint32_t);
+	if (needsz > *targetsz) {
+		nt = realloc(*target, needsz);
+		if (nt == NULL)
+			return rect->data;
+		*target = nt;
+		*targetsz = needsz;
+	}
+	ctarget = *target;
+
+	// And if we need an extra target, do the same there...
+	if (pointy3 + pointy5 + xbr4 + xbr2 > 1) {
+		if (needsz > tmptargetsz) {
+			nt = realloc(tmptarget, needsz);
+			if (nt == NULL)
+				return rect->data;
+			tmptarget = nt;
+			tmptargetsz = needsz;
+		}
+	}
+
+	// And finally, scale...
+	while (xbr4 > 0) {
+		xbr_filter(csrc, ctarget, swidth, sheight, 4);
+		xbr4--;
+		swidth *= 4;
+		sheight *= 4;
+		csrc = ctarget;
+		if (ctarget == tmptarget)
+			ctarget = *target;
+		else
+			ctarget = tmptarget;
+	}
+	while (xbr2 > 0) {
+		xbr_filter(csrc, ctarget, swidth, sheight, 2);
+		xbr2--;
+		swidth *= 2;
+		sheight *= 2;
+		csrc = ctarget;
+		if (ctarget == tmptarget)
+			ctarget = *target;
+		else
+			ctarget = tmptarget;
+	}
+	while (pointy5 > 0) {
+		pointy_scale5(csrc, ctarget, swidth, sheight);
+		pointy5--;
+		swidth *= 5;
+		sheight *= 5;
+		csrc = ctarget;
+		if (ctarget == tmptarget)
+			ctarget = *target;
+		else
+			ctarget = tmptarget;
+	}
+	while (pointy3 > 0) {
+		pointy_scale3(csrc, ctarget, swidth, sheight);
+		pointy3--;
+		swidth *= 3;
+		sheight *= 3;
+		csrc = ctarget;
+		if (ctarget == tmptarget)
+			ctarget = *target;
+		else
+			ctarget = tmptarget;
+	}
+	*w *= *xscale;
+	*h *= *xscale;
+	*xscale = newscale;
+	*yscale = newscale;
+	return csrc;
+}
+
+static void
+pointy_scale5(uint32_t* src, uint32_t* dest, int width, int height)
+{
+	int x, y;
+	uint32_t* s;
+	uint32_t* d;
+	int w5 = width * 5;
+	int w10 = width * 10;
+	int w15 = width * 15;
+	int w20 = width * 20;
+	int prevline, prevcol, nextline, nextcol;
+
+	s = src;
+	d = dest;
+	prevline = 0;
+	nextline = width;
+	for (y = 0; y < height; y++) {
+		if (y == height - 1)
+			nextline = 0;
+		prevcol = 0;
+		nextcol = 1;
+		for (x = 0; x < width; x++) {
+			if (x == width - 1)
+				nextcol = 0;
+
+			if (s[prevline + prevcol] == s[0]) {
+				d[0] = s[0];
+				d[1] = s[0];
+				d[w5] = s[0];
+			}
+			else if (s[prevcol] == s[prevline]) {
+				d[0] = s[prevcol];
+				d[1] = s[prevcol];
+				d[w5] = s[prevcol];
+			}
+			else {
+				d[0] = *s;
+				d[1] = *s;
+				d[w5] = *s;
+			}
+
+			// Top-middle stays OG.
+			d[2] = *s;
+			d[w5+1] = *s;
+			d[w5+2] = *s;
+			d[w5+3] = *s;
+
+			// And so on around the outside (round the outside)
+			if (s[prevline + nextcol] == s[0]) {
+				d[3] = s[0];
+				d[4] = s[0];
+				d[w5 + 4] = s[0];
+			}
+			else if (s[nextcol] == s[prevline]) {
+				d[3] = s[nextcol];
+				d[4] = s[nextcol];
+				d[w5 + 4] = s[nextcol];
+			}
+			else {
+				d[3] = s[0];
+				d[4] = s[0];
+				d[w5 + 4] = s[0];
+			}
+
+			d[w10] = *s;
+			d[w10+1] = *s;
+			d[w10+2] = *s;
+			d[w10+3] = *s;
+			d[w10+4] = *s;
+
+			if (s[prevcol + nextline] == s[0]) {
+				d[w15] = s[0];
+				d[w20] = s[0];
+				d[w20 + 1] = s[0];
+			}
+			else if(s[prevcol] == s[nextline]) {
+				d[w15] = s[prevcol];
+				d[w20] = s[prevcol];
+				d[w20 + 1] = s[prevcol];
+			}
+			else {
+				d[w15] = s[0];
+				d[w20] = s[0];
+				d[w20 + 1] = s[0];
+			}
+
+			d[w15 + 1] = *s;
+			d[w15 + 2] = *s;
+			d[w15 + 3] = *s;
+			d[w20 + 2] = *s;
+
+			if (s[nextcol + nextline] == s[0]) {
+				d[w15 + 4] = s[0];
+				d[w20 + 3] = s[0];
+				d[w20 + 4] = s[0];
+			}
+			else if (s[nextcol] == s[nextline]) {
+				d[w15 + 4] = s[nextcol];
+				d[w20 + 3] = s[nextcol];
+				d[w20 + 4] = s[nextcol];
+			}
+			else {
+				d[w15 + 4] = s[0];
+				d[w20 + 3] = s[0];
+				d[w20 + 4] = s[0];
+			}
+
+			d += 5;
+			s++;
+
+			if (x == 0)
+				prevcol = -1;
+		}
+		d += w20;
+		if (y == 0)
+			prevline = -width;
+	}
+}
+
+static void
+pointy_scale3(uint32_t* src, uint32_t* dest, int width, int height)
+{
+	int x, y;
+	uint32_t* s;
+	uint32_t* d;
+	int w3 = width * 3;
+	int w6 = width * 6;
+	int prevline, prevcol, nextline, nextcol;
+
+	s = src;
+	d = dest;
+	prevline = 0;
+	nextline = width;
+	for (y = 0; y < height; y++) {
+		if (y == height - 1)
+			nextline = 0;
+		prevcol = 0;
+		nextcol = 1;
+		for (x = 0; x < width; x++) {
+			if (x == width - 1)
+				nextcol = 0;
+
+			// Top-left is filled if both left and top are the same.
+			if (s[prevline + prevcol] == s[0])
+				d[0] = s[0];
+			else if (s[prevcol] == s[prevline])
+				d[0] = s[prevcol];
+			else
+				d[0] = *s;
+
+			// Top-middle stays OG.
+			d[1] = *s;
+
+			// And so on around the outside (round the outside)
+			if (s[prevline + nextcol] == s[0])
+				d[2] = s[0];
+			else if (s[nextcol] == s[prevline])
+				d[2] = s[nextcol];
+			else
+				d[2] = *s;
+
+			d[w3] = *s;
+			d[w3 + 1] = *s;
+			d[w3 + 2] = *s;
+
+			if (s[prevcol + nextline] == s[0])
+				d[w6] = s[0];
+			else if(s[prevcol] == s[nextline])
+				d[w6] = s[prevcol];
+			else
+				d[w6] = *s;
+
+			d[w6 + 1] = *s;
+
+			if (s[nextcol + nextline] == s[0])
+				d[w6 + 2] = s[0];
+			else if (s[nextcol] == s[nextline])
+				d[w6 + 2] = s[nextcol];
+			else
+				d[w6 + 2] = s[0];
+
+			d += 3;
+			s++;
+
+			if (x == 0)
+				prevcol = -1;
+		}
+		d += w6;
+		if (y == 0)
+			prevline = -width;
+	}
+}
diff --git a/src/conio/scale.h b/src/conio/scale.h
new file mode 100644
index 0000000000000000000000000000000000000000..b867d034b669190cb182f1eecbe6ff0de3252b71
--- /dev/null
+++ b/src/conio/scale.h
@@ -0,0 +1,3 @@
+#include "bitmap_con.h"
+
+uint32_t* do_scale(struct rectlist* rect, uint32_t **target, size_t *targetsz, int* xscale, int* yscale, int* w, int* h);
diff --git a/src/conio/x_events.c b/src/conio/x_events.c
index ace6613adcd39e1aed2f09a95de5f930c2668647..e3014eedf551fe6ed7e1ff041fb1373fb3bfe17c 100644
--- a/src/conio/x_events.c
+++ b/src/conio/x_events.c
@@ -27,6 +27,7 @@
 #define BITMAP_CIOLIB_DRIVER
 #include "bitmap_con.h"
 #include "link_list.h"
+#include "scale.h"
 #include "x_events.h"
 #include "x_cio.h"
 #include "utf8_codepages.h"
@@ -449,16 +450,25 @@ static int video_init()
 static void local_draw_rect(struct rectlist *rect)
 {
 	int x,y,xscale,yscale,xoff=0,yoff=0;
+	int xscaling, yscaling;
+	int rxscaling, ryscaling;
 	unsigned int r, g, b;
 	unsigned long pixel;
-	int cleft = rect->rect.width;
-	int cright = -1;
-	int ctop = rect->rect.height;
+	int cleft;
+	int cright = -100;
+	int ctop;
 	int cbottom = -1;
 	int idx;
+	int ridx;
+	int ridx_part;
 	uint32_t last_pixel = 0x55555555;
+	static uint32_t* target = NULL;
+	size_t targetsz = 0;
+	uint32_t* source;
+	int width, height;
+	int lheight, lines;
 
-	if (bitmap_width != cleft || bitmap_height != ctop)
+	if (bitmap_width != rect->rect.width || bitmap_height != rect->rect.height)
 		return;
 
 	xoff = (x11_window_width - xim->width) / 2;
@@ -468,12 +478,26 @@ static void local_draw_rect(struct rectlist *rect)
 	if (yoff < 0)
 		yoff = 0;
 
+	// Scale...
+	xscaling = x_cvstat.scaling;
+	yscaling = x_cvstat.scaling*x_cvstat.vmultiplier;
+	source = do_scale(rect, &target, &targetsz, &xscaling, &yscaling, &width, &height);
+	rxscaling = width / rect->rect.width;
+	ryscaling = height / rect->rect.height;
+	cleft = width;
+	ctop = height;
+	lines = 0;
+	lheight = x_cvstat.charheight * ryscaling;
+
 	/* TODO: Translate into local colour depth */
-	for(y=0;y<rect->rect.height;y++) {
-		idx = y*rect->rect.width;
-		for(x=0; x<rect->rect.width; x++) {
+	for(y=0;y<height;y++) {
+		idx = y * width;
+		ridx = y / ryscaling * rect->rect.width;
+		ridx_part = 0;
+		for(x=0; x<width; x++) {
 			if (last) {
-				if (last->data[idx] != rect->data[idx]) {
+				// TODO: Based on source pixel, not target pixel. :(
+				if (last->data[ridx] != rect->data[ridx]) {
 					if (x < cleft)
 						cleft = x;
 					if (x > cright)
@@ -484,15 +508,29 @@ static void local_draw_rect(struct rectlist *rect)
 						cbottom = y;
 				}
 				else {
-					idx++;
-					continue;
+					if (source == rect->data) {
+						idx += (rxscaling);
+						x += (rxscaling - 1);
+						ridx++;
+						continue;
+					}
+					else {
+						if (cright < x - rxscaling * 2
+						    && (x >= width - (rxscaling * 3)
+						    || last->data[ridx + 1] == rect->data[ridx + 1])) {
+							idx += (rxscaling);
+							x += (rxscaling - 1);
+							ridx++;
+							continue;
+						}
+					}
 				}
 			}
-			if (last_pixel != rect->data[idx]) {
-				last_pixel = rect->data[idx];
-				r = rect->data[idx] >> 16 & 0xff;
-				g = rect->data[idx] >> 8 & 0xff;
-				b = rect->data[idx] & 0xff;
+			if (last_pixel != source[idx]) {
+				last_pixel = source[idx];
+				r = source[idx] >> 16 & 0xff;
+				g = source[idx] >> 8 & 0xff;
+				b = source[idx] & 0xff;
 				r = (r<<8)|r;
 				g = (g<<8)|g;
 				b = (b<<8)|b;
@@ -510,28 +548,49 @@ static void local_draw_rect(struct rectlist *rect)
 				else
 					pixel |= (b >> (0-b_shift)) & visual.blue_mask;
 			}
-			for(yscale=0; yscale<x_cvstat.scaling*x_cvstat.vmultiplier; yscale++) {
-				for(xscale=0; xscale<x_cvstat.scaling; xscale++) {
+			for(yscale=0; yscale<yscaling; yscale++) {
+				for(xscale=0; xscale<xscaling; xscale++) {
 #ifdef XPutPixel
-					XPutPixel(xim,(x+rect->rect.x)*x_cvstat.scaling+xscale,(y+rect->rect.y)*x_cvstat.scaling*x_cvstat.vmultiplier+yscale,pixel);
+					XPutPixel(xim, (x + rect->rect.x) * xscaling + xscale
+					    , (y + rect->rect.y) * yscaling + yscale, pixel);
 #else
-					x11.XPutPixel(xim,(x+rect->rect.x)*x_cvstat.scaling+xscale,(y+rect->rect.y)*x_cvstat.scaling*x_cvstat.vmultiplier+yscale,pixel);
+					x11.XPutPixel(xim, (x + rect->rect.x) * xscaling + xscale
+					    , (y + rect->rect.y) * yscaling + yscale, pixel);
 #endif
 				}
 			}
 			idx++;
+			ridx_part++;
+			if (ridx_part >= rxscaling) {
+				ridx_part = 0;
+				ridx++;
+			}
 		}
+		lines++;
 		/* This line was changed */
-		if (last && (((y & 0x1f) == 0x1f) || (y == rect->rect.height-1)) && cright >= 0) {
-			x11.XPutImage(dpy, win, gc, xim, cleft*x_cvstat.scaling, ctop*x_cvstat.scaling*x_cvstat.vmultiplier, cleft*x_cvstat.scaling + xoff, ctop*x_cvstat.scaling*x_cvstat.vmultiplier + yoff, (cright-cleft+1)*x_cvstat.scaling, (cbottom-ctop+1)*x_cvstat.scaling*x_cvstat.vmultiplier);
-			cleft = rect->rect.width;
-			cright = cbottom = -1;
-			ctop = rect->rect.height;
+		if (last && ((lines == lheight) || (y == height - 1)) && cright >= 0) {
+#ifdef USE_XBRZ
+			if (source != rect->data) {
+				cleft -= 2 * rxscaling;
+				if (cleft < 0)
+					cleft = 0;
+				cright += 2 * rxscaling;
+				if (cright >= width)
+					cright = width - 1;
+			}
+#endif
+			lines = 0;
+			x11.XPutImage(dpy, win, gc, xim, cleft * xscaling, ctop * yscaling
+			    , cleft * xscaling + xoff, ctop * yscaling + yoff
+			    , (cright - cleft + 1) * xscaling, (cbottom - ctop + 1) * yscaling);
+			cleft = width;
+			cright = cbottom = -100;
+			ctop = height;
 		}
 	}
 
 	if (last == NULL)
-		x11.XPutImage(dpy, win, gc, xim, rect->rect.x*x_cvstat.scaling, rect->rect.y*x_cvstat.scaling*x_cvstat.vmultiplier, rect->rect.x*x_cvstat.scaling + xoff, rect->rect.y*x_cvstat.scaling*x_cvstat.vmultiplier + yoff, rect->rect.width*x_cvstat.scaling, rect->rect.height*x_cvstat.scaling*x_cvstat.vmultiplier);
+		x11.XPutImage(dpy, win, gc, xim, rect->rect.x*xscaling, rect->rect.y*yscaling, rect->rect.x*xscaling + xoff, rect->rect.y*yscaling + yoff, width * xscaling, height * yscaling);
 	else
 		bitmap_drv_free_rect(last);
 	last = rect;
@@ -644,11 +703,14 @@ static int x11_event(XEvent *ev)
 			break;
 		/* Graphics related events */
 		case ConfigureNotify:
-			x11_window_xpos=ev->xconfigure.x;
-			x11_window_ypos=ev->xconfigure.y;
-			x11_window_width=ev->xconfigure.width;
-			x11_window_height=ev->xconfigure.height;
-			handle_resize_event(ev->xconfigure.width, ev->xconfigure.height);
+			if (x11_window_xpos != ev->xconfigure.x || x11_window_ypos != ev->xconfigure.y
+			    || x11_window_width != ev->xconfigure.width || x11_window_height != ev->xconfigure.height) {
+				x11_window_xpos=ev->xconfigure.x;
+				x11_window_ypos=ev->xconfigure.y;
+				x11_window_width=ev->xconfigure.width;
+				x11_window_height=ev->xconfigure.height;
+				handle_resize_event(ev->xconfigure.width, ev->xconfigure.height);
+			}
 			break;
 		case NoExpose:
 			break;
diff --git a/src/conio/xbr.c b/src/conio/xbr.c
new file mode 100644
index 0000000000000000000000000000000000000000..64211daea20f668454644700cf2602f1e5ff1fde
--- /dev/null
+++ b/src/conio/xbr.c
@@ -0,0 +1,293 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * Copyright (c) 2011, 2012 Hyllian/Jararaca <sergiogdb@gmail.com>
+ * Copyright (c) 2014 Arwa Arif <arwaarif1994@gmail.com>
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * XBR Filter is used for depixelization of image.
+ * This is based on Hyllian's xBR shader.
+ *
+ * @see https://forums.libretro.com/t/xbr-algorithm-tutorial/123
+ * @see https://github.com/yoyofr/iFBA/blob/master/fba_src/src/intf/video/scalers/xbr.cpp
+ */
+
+#include <inttypes.h>
+#include <stdlib.h>
+
+#define LB_MASK       0x00FEFEFE
+#define RED_BLUE_MASK 0x00FF00FF
+#define GREEN_MASK    0x0000FF00
+
+#ifdef PI
+#undef PI
+#endif
+
+uint32_t r2y[1<<24];
+
+static uint32_t pixel_diff(uint32_t x, uint32_t y)
+{
+#define YMASK 0xff0000
+#define UMASK 0x00ff00
+#define VMASK 0x0000ff
+#define ABSDIFF(a,b) (abs((int)(a)-(int)(b)))
+
+    uint32_t yuv1 = r2y[x & 0xffffff];
+
+    uint32_t yuv2 = r2y[y & 0xffffff];
+
+    return (ABSDIFF(yuv1 & YMASK, yuv2 & YMASK) >> 16) +
+           (ABSDIFF(yuv1 & UMASK, yuv2 & UMASK) >>  8) +
+            ABSDIFF(yuv1 & VMASK, yuv2 & VMASK);
+}
+
+#define ALPHA_BLEND_128_W(a, b) ((((a) & LB_MASK) >> 1) + (((b) & LB_MASK) >> 1))
+#define ALPHA_BLEND_BASE(a, b, m, s) (  (RED_BLUE_MASK & (((a) & RED_BLUE_MASK) + (((((b) & RED_BLUE_MASK) - ((a) & RED_BLUE_MASK)) * (m)) >> (s)))) \
+                                      | (GREEN_MASK    & (((a) & GREEN_MASK)    + (((((b) & GREEN_MASK)    - ((a) & GREEN_MASK))    * (m)) >> (s)))))
+#define ALPHA_BLEND_32_W(a, b)  ALPHA_BLEND_BASE(a, b, 1, 3)
+#define ALPHA_BLEND_64_W(a, b)  ALPHA_BLEND_BASE(a, b, 1, 2)
+#define ALPHA_BLEND_192_W(a, b) ALPHA_BLEND_BASE(a, b, 3, 2)
+#define ALPHA_BLEND_224_W(a, b) ALPHA_BLEND_BASE(a, b, 7, 3)
+
+#define df(A, B) pixel_diff(A, B)
+#define eq(A, B) (df(A, B) < 155)
+
+#define FILT2(PE, PI, PH, PF, PG, PC, PD, PB, PA, G5, C4, G0, D0, C1, B1, F4, I4, H5, I5, A0, A1,   \
+              N0, N1, N2, N3) do {                                                                  \
+    if (PE != PH && PE != PF) {                                                                     \
+        const unsigned e = df(PE,PC) + df(PE,PG) + df(PI,H5) + df(PI,F4) + (df(PH,PF)<<2);          \
+        const unsigned i = df(PH,PD) + df(PH,I5) + df(PF,I4) + df(PF,PB) + (df(PE,PI)<<2);          \
+        if (e <= i) {                                                                               \
+            const unsigned px = df(PE,PF) <= df(PE,PH) ? PF : PH;                                   \
+            if (e < i && (!eq(PF,PB) && !eq(PH,PD) || eq(PE,PI)                                     \
+                          && (!eq(PF,I4) && !eq(PH,I5))                                             \
+                          || eq(PE,PG) || eq(PE,PC))) {                                             \
+                const unsigned ke = df(PF,PG);                                                      \
+                const unsigned ki = df(PH,PC);                                                      \
+                const int left    = ke<<1 <= ki && PE != PG && PD != PG;                            \
+                const int up      = ke >= ki<<1 && PE != PC && PB != PC;                            \
+                if (left && up) {                                                                   \
+                    E[N3] = ALPHA_BLEND_224_W(E[N3], px);                                           \
+                    E[N2] = ALPHA_BLEND_64_W( E[N2], px);                                           \
+                    E[N1] = E[N2];                                                                  \
+                } else if (left) {                                                                  \
+                    E[N3] = ALPHA_BLEND_192_W(E[N3], px);                                           \
+                    E[N2] = ALPHA_BLEND_64_W( E[N2], px);                                           \
+                } else if (up) {                                                                    \
+                    E[N3] = ALPHA_BLEND_192_W(E[N3], px);                                           \
+                    E[N1] = ALPHA_BLEND_64_W( E[N1], px);                                           \
+                } else { /* diagonal */                                                             \
+                    E[N3] = ALPHA_BLEND_128_W(E[N3], px);                                           \
+                }                                                                                   \
+            } else {                                                                                \
+                E[N3] = ALPHA_BLEND_128_W(E[N3], px);                                               \
+            }                                                                                       \
+        }                                                                                           \
+    }                                                                                               \
+} while (0)
+
+#define FILT3(PE, PI, PH, PF, PG, PC, PD, PB, PA, G5, C4, G0, D0, C1, B1, F4, I4, H5, I5, A0, A1,   \
+              N0, N1, N2, N3, N4, N5, N6, N7, N8) do {                                              \
+    if (PE != PH && PE != PF) {                                                                     \
+        const unsigned e = df(PE,PC) + df(PE,PG) + df(PI,H5) + df(PI,F4) + (df(PH,PF)<<2);          \
+        const unsigned i = df(PH,PD) + df(PH,I5) + df(PF,I4) + df(PF,PB) + (df(PE,PI)<<2);          \
+        if (e <= i) {                                                                               \
+            const unsigned px = df(PE,PF) <= df(PE,PH) ? PF : PH;                                   \
+            if (e < i && (!eq(PF,PB) && !eq(PF,PC) || !eq(PH,PD) && !eq(PH,PG) || eq(PE,PI)         \
+                          && (!eq(PF,F4) && !eq(PF,I4) || !eq(PH,H5) && !eq(PH,I5))                 \
+                          || eq(PE,PG) || eq(PE,PC))) {                                             \
+                const unsigned ke = df(PF,PG);                                                      \
+                const unsigned ki = df(PH,PC);                                                      \
+                const int left    = ke<<1 <= ki && PE != PG && PD != PG;                            \
+                const int up      = ke >= ki<<1 && PE != PC && PB != PC;                            \
+                if (left && up) {                                                                   \
+                    E[N7] = ALPHA_BLEND_192_W(E[N7], px);                                           \
+                    E[N6] = ALPHA_BLEND_64_W( E[N6], px);                                           \
+                    E[N5] = E[N7];                                                                  \
+                    E[N2] = E[N6];                                                                  \
+                    E[N8] = px;                                                                     \
+                } else if (left) {                                                                  \
+                    E[N7] = ALPHA_BLEND_192_W(E[N7], px);                                           \
+                    E[N5] = ALPHA_BLEND_64_W( E[N5], px);                                           \
+                    E[N6] = ALPHA_BLEND_64_W( E[N6], px);                                           \
+                    E[N8] = px;                                                                     \
+                } else if (up) {                                                                    \
+                    E[N5] = ALPHA_BLEND_192_W(E[N5], px);                                           \
+                    E[N7] = ALPHA_BLEND_64_W( E[N7], px);                                           \
+                    E[N2] = ALPHA_BLEND_64_W( E[N2], px);                                           \
+                    E[N8] = px;                                                                     \
+                } else { /* diagonal */                                                             \
+                    E[N8] = ALPHA_BLEND_224_W(E[N8], px);                                           \
+                    E[N5] = ALPHA_BLEND_32_W( E[N5], px);                                           \
+                    E[N7] = ALPHA_BLEND_32_W( E[N7], px);                                           \
+                }                                                                                   \
+            } else {                                                                                \
+                E[N8] = ALPHA_BLEND_128_W(E[N8], px);                                               \
+            }                                                                                       \
+        }                                                                                           \
+    }                                                                                               \
+} while (0)
+
+#define FILT4(PE, PI, PH, PF, PG, PC, PD, PB, PA, G5, C4, G0, D0, C1, B1, F4, I4, H5, I5, A0, A1,   \
+              N15, N14, N11, N3, N7, N10, N13, N12, N9, N6, N2, N1, N5, N8, N4, N0) do {            \
+    if (PE != PH && PE != PF) {                                                                     \
+        const unsigned e = df(PE,PC) + df(PE,PG) + df(PI,H5) + df(PI,F4) + (df(PH,PF)<<2);          \
+        const unsigned i = df(PH,PD) + df(PH,I5) + df(PF,I4) + df(PF,PB) + (df(PE,PI)<<2);          \
+        if (e <= i) {                                                                               \
+            const unsigned px = df(PE,PF) <= df(PE,PH) ? PF : PH;                                   \
+            if (e < i && (!eq(PF,PB) && !eq(PH,PD) || eq(PE,PI)                                     \
+                          && (!eq(PF,I4) && !eq(PH,I5))                                             \
+                          || eq(PE,PG) || eq(PE,PC))) {                                             \
+                const unsigned ke = df(PF,PG);                                                      \
+                const unsigned ki = df(PH,PC);                                                      \
+                const int left    = ke<<1 <= ki && PE != PG && PD != PG;                            \
+                const int up      = ke >= ki<<1 && PE != PC && PB != PC;                            \
+                if (left && up) {                                                                   \
+                    E[N13] = ALPHA_BLEND_192_W(E[N13], px);                                         \
+                    E[N12] = ALPHA_BLEND_64_W( E[N12], px);                                         \
+                    E[N15] = E[N14] = E[N11] = px;                                                  \
+                    E[N10] = E[N3]  = E[N12];                                                       \
+                    E[N7]  = E[N13];                                                                \
+                } else if (left) {                                                                  \
+                    E[N11] = ALPHA_BLEND_192_W(E[N11], px);                                         \
+                    E[N13] = ALPHA_BLEND_192_W(E[N13], px);                                         \
+                    E[N10] = ALPHA_BLEND_64_W( E[N10], px);                                         \
+                    E[N12] = ALPHA_BLEND_64_W( E[N12], px);                                         \
+                    E[N14] = px;                                                                    \
+                    E[N15] = px;                                                                    \
+                } else if (up) {                                                                    \
+                    E[N14] = ALPHA_BLEND_192_W(E[N14], px);                                         \
+                    E[N7 ] = ALPHA_BLEND_192_W(E[N7 ], px);                                         \
+                    E[N10] = ALPHA_BLEND_64_W( E[N10], px);                                         \
+                    E[N3 ] = ALPHA_BLEND_64_W( E[N3 ], px);                                         \
+                    E[N11] = px;                                                                    \
+                    E[N15] = px;                                                                    \
+                } else { /* diagonal */                                                             \
+                    E[N11] = ALPHA_BLEND_128_W(E[N11], px);                                         \
+                    E[N14] = ALPHA_BLEND_128_W(E[N14], px);                                         \
+                    E[N15] = px;                                                                    \
+                }                                                                                   \
+            } else {                                                                                \
+                E[N15] = ALPHA_BLEND_128_W(E[N15], px);                                             \
+            }                                                                                       \
+        }                                                                                           \
+    }                                                                                               \
+} while (0)
+
+void
+xbr_filter(uint32_t *data, uint32_t *out, int width, int height, int n)
+{
+    int x, y;
+    const int nl = width * n;
+    const int nl1 = nl + nl;
+    const int nl2 = nl1 + nl;
+
+    for (y = 0; y < height; y++) {
+
+        uint32_t *E = out + y * width * n * n;
+        const uint32_t *sa2 = (data + y * width - 2); /* center */
+        const uint32_t *sa1 = sa2 - (width); /* up x1 */
+        const uint32_t *sa0 = sa1 - (width); /* up x2 */
+        const uint32_t *sa3 = sa2 + (width); /* down x1 */
+        const uint32_t *sa4 = sa3 + (width); /* down x2 */
+
+        if (y <= 1) {
+            sa0 = sa1;
+            if (y == 0) {
+                sa0 = sa1 = sa2;
+            }
+        }
+
+        if (y >= height - 2) {
+            sa4 = sa3;
+            if (y == height - 1) {
+                sa4 = sa3 = sa2;
+            }
+        }
+
+        for (x = 0; x < width; x++) {
+            const uint32_t B1 = sa0[2];
+            const uint32_t PB = sa1[2];
+            const uint32_t PE = sa2[2];
+            const uint32_t PH = sa3[2];
+            const uint32_t H5 = sa4[2];
+
+            const int pprev = 2 - (x > 0);
+            const uint32_t A1 = sa0[pprev];
+            const uint32_t PA = sa1[pprev];
+            const uint32_t PD = sa2[pprev];
+            const uint32_t PG = sa3[pprev];
+            const uint32_t G5 = sa4[pprev];
+
+            const int pprev2 = pprev - (x > 1);
+            const uint32_t A0 = sa1[pprev2];
+            const uint32_t D0 = sa2[pprev2];
+            const uint32_t G0 = sa3[pprev2];
+
+            const int pnext = 3 - (x == width - 1);
+            const uint32_t C1 = sa0[pnext];
+            const uint32_t PC = sa1[pnext];
+            const uint32_t PF = sa2[pnext];
+            const uint32_t PI = sa3[pnext];
+            const uint32_t I5 = sa4[pnext];
+
+            const int pnext2 = pnext + 1 - (x >= width - 2);
+            const uint32_t C4 = sa1[pnext2];
+            const uint32_t F4 = sa2[pnext2];
+            const uint32_t I4 = sa3[pnext2];
+
+            if (n == 2) {
+                E[0]  = E[1]      =     // 0, 1
+                E[nl] = E[nl + 1] = PE; // 2, 3
+
+                FILT2(PE, PI, PH, PF, PG, PC, PD, PB, PA, G5, C4, G0, D0, C1, B1, F4, I4, H5, I5, A0, A1, 0, 1, nl, nl+1);
+                FILT2(PE, PC, PF, PB, PI, PA, PH, PD, PG, I4, A1, I5, H5, A0, D0, B1, C1, F4, C4, G5, G0, nl, 0, nl+1, 1);
+                FILT2(PE, PA, PB, PD, PC, PG, PF, PH, PI, C1, G0, C4, F4, G5, H5, D0, A0, B1, A1, I4, I5, nl+1, nl, 1, 0);
+                FILT2(PE, PG, PD, PH, PA, PI, PB, PF, PC, A0, I5, A1, B1, I4, F4, H5, G5, D0, G0, C1, C4, 1, nl+1, 0, nl);
+            } else if (n == 3) {
+                E[0]   = E[1]     = E[2]     =     // 0, 1, 2
+                E[nl]  = E[nl+1]  = E[nl+2]  =     // 3, 4, 5
+                E[nl1] = E[nl1+1] = E[nl1+2] = PE; // 6, 7, 8
+
+                FILT3(PE, PI, PH, PF, PG, PC, PD, PB, PA, G5, C4, G0, D0, C1, B1, F4, I4, H5, I5, A0, A1, 0, 1, 2, nl, nl+1, nl+2, nl1, nl1+1, nl1+2);
+                FILT3(PE, PC, PF, PB, PI, PA, PH, PD, PG, I4, A1, I5, H5, A0, D0, B1, C1, F4, C4, G5, G0, nl1, nl, 0, nl1+1, nl+1, 1, nl1+2, nl+2, 2);
+                FILT3(PE, PA, PB, PD, PC, PG, PF, PH, PI, C1, G0, C4, F4, G5, H5, D0, A0, B1, A1, I4, I5, nl1+2, nl1+1, nl1, nl+2, nl+1, nl, 2, 1, 0);
+                FILT3(PE, PG, PD, PH, PA, PI, PB, PF, PC, A0, I5, A1, B1, I4, F4, H5, G5, D0, G0, C1, C4, 2, nl+2, nl1+2, 1, nl+1, nl1+1, 0, nl, nl1);
+            } else if (n == 4) {
+                E[0]   = E[1]     = E[2]     = E[3]     =     //  0,  1,  2,  3
+                E[nl]  = E[nl+1]  = E[nl+2]  = E[nl+3]  =     //  4,  5,  6,  7
+                E[nl1] = E[nl1+1] = E[nl1+2] = E[nl1+3] =     //  8,  9, 10, 11
+                E[nl2] = E[nl2+1] = E[nl2+2] = E[nl2+3] = PE; // 12, 13, 14, 15
+
+                FILT4(PE, PI, PH, PF, PG, PC, PD, PB, PA, G5, C4, G0, D0, C1, B1, F4, I4, H5, I5, A0, A1, nl2+3, nl2+2, nl1+3, 3, nl+3, nl1+2, nl2+1, nl2, nl1+1, nl+2, 2, 1, nl+1, nl1, nl, 0);
+                FILT4(PE, PC, PF, PB, PI, PA, PH, PD, PG, I4, A1, I5, H5, A0, D0, B1, C1, F4, C4, G5, G0, 3, nl+3, 2, 0, 1, nl+2, nl1+3, nl2+3, nl1+2, nl+1, nl, nl1, nl1+1, nl2+2, nl2+1, nl2);
+                FILT4(PE, PA, PB, PD, PC, PG, PF, PH, PI, C1, G0, C4, F4, G5, H5, D0, A0, B1, A1, I4, I5, 0, 1, nl, nl2, nl1, nl+1, 2, 3, nl+2, nl1+1, nl2+1, nl2+2, nl1+2, nl+3, nl1+3, nl2+3);
+                FILT4(PE, PG, PD, PH, PA, PI, PB, PF, PC, A0, I5, A1, B1, I4, F4, H5, G5, D0, G0, C1, C4, nl2, nl1, nl2+1, nl2+3, nl2+2, nl1+1, nl, 0, nl+1, nl1+2, nl1+3, nl+3, nl+2, 1, 2, 3);
+            }
+
+            sa0 += 1;
+            sa1 += 1;
+            sa2 += 1;
+            sa3 += 1;
+            sa4 += 1;
+
+            E += n;
+        }
+    }
+}
diff --git a/src/conio/xbr.h b/src/conio/xbr.h
new file mode 100644
index 0000000000000000000000000000000000000000..c8c32aeb647a5f3ab1410368cd7a02cb5f9589c2
--- /dev/null
+++ b/src/conio/xbr.h
@@ -0,0 +1,4 @@
+#include <inttypes.h>
+
+void xbr_filter(uint32_t *data, uint32_t *out, int width, int height, int n);
+