Reorganice los píxeles en la imagen para que no se pueda reconocer y luego recuperarlo

86

Cree un programa que pueda reorganizar píxeles en la imagen para que no pueda ser reconocido. Sin embargo, su programa debería poder convertirlo nuevamente a la imagen original.

Puede escribir dos funciones, para codificar y decodificar, sin embargo, una función que se aplica repetidamente da una imagen original (ejemplo en matemáticas f(x) = 1 - x) es una ventaja.

También producir algún patrón en la salida también da bonificación.

La imagen puede representarse como una matriz 1D / 2D u objeto de imagen si su idioma lo admite. Tenga en cuenta que solo puede cambiar el orden de los píxeles.

Será lógico seleccionar como código ganador que produce una imagen menos reconocible, sin embargo, no sé cómo medirlo exactamente, todas las formas que puedo imaginar pueden ser engañadas. Por lo tanto, elegí esta pregunta como concurso de popularidad. ¡Permita que los usuarios elijan cuál es la mejor respuesta!

Imagen de prueba 1 (800 x 422 px): Imagen de prueba 2 (800 x 480 px): Proporcione la imagen de salida del código.

Somnium
fuente
La pregunta es una forma muy larga de decir "Escribe un algoritmo de cifrado para imágenes, cuyo resultado es una imagen".
David Richerby
3
@DavidRicherby ... que usa los mismos píxeles / colores. Cinco píxeles negros en la "imagen simple" -> cinco píxeles negros en la "imagen cifrada".
Daniel Beck
2
@ user2992539 Muy bien, en ese caso es posible que desee indicar explícitamente que esto se usa como desempate. De lo contrario, solo decir que es un bono no es muy significativo.
Martin Ender
3
Esta pregunta me hizo pensar en el mapa del gato de Arnold . No creo que sea adecuado para este propósito, pero es interesante de la misma manera: repetir el mapa suficientes veces te lleva de vuelta a la imagen original.
trichoplax
44
Ahora en otra parte de la red de Stack Exchange: Seguridad
SE

Respuestas:

58

Python 2.7 (con PIL): sin seudoaleatoriedad

Divido la imagen en 2 por 2 bloques (ignorando el resto) y gire cada bloque 180 grados, luego hago lo mismo con 3 por 3 bloques, luego 4, etc. hasta algún parámetro BLKSZ. Luego hago lo mismo para BLKSZ-1, luego BLKSZ-2, hasta 3, luego 2. Este método se invierte exactamente; la función de descifrar es la función de codificación.

El código :

from PIL import Image
import math

im = Image.open("ST1.png", "r")
arr = im.load() #pixel data stored in this 2D array

def rot(A, n, x1, y1): #this is the function which rotates a given block
    temple = []
    for i in range(n):
        temple.append([])
        for j in range(n):
            temple[i].append(arr[x1+i, y1+j])
    for i in range(n):
        for j in range(n):
            arr[x1+i,y1+j] = temple[n-1-i][n-1-j]


xres = 800
yres = 480
BLKSZ = 50 #blocksize
for i in range(2, BLKSZ+1):
    for j in range(int(math.floor(float(xres)/float(i)))):
        for k in range(int(math.floor(float(yres)/float(i)))):
            rot(arr, i, j*i, k*i)
for i in range(3, BLKSZ+1):
    for j in range(int(math.floor(float(xres)/float(BLKSZ+2-i)))):
        for k in range(int(math.floor(float(yres)/float(BLKSZ+2-i)))):
            rot(arr, BLKSZ+2-i, j*(BLKSZ+2-i), k*(BLKSZ+2-i))

im.save("ST1OUT "+str(BLKSZ)+".png")

print("Done!")

Dependiendo del tamaño de bloque, puede hacer que el cálculo erradique todo parecido con la imagen original: (BLKSZ = 50) ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

O haga que el cálculo sea eficiente: (BLKSZ = 10) ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

GH Faust
fuente
66
Parece que los mejores resultados serán si BLKSZ tendrá aproximadamente la mitad del tamaño de la imagen. De todos modos, ¡me gusta el algoritmo y para BLKSZ pequeño parece un arte moderno! ¡Bueno!
Somnium
11
Siempre voté por Python.
qwr
En lugar de codificar todos los valores del 2 al 50, ¿tal vez debería usar solo números primos?
Neil
@Neil Probablemente entonces se verá más aleatorio y menos artístico.
Somnium
¡El BLKSZ = 10paisaje es realmente genial!
wchargin
52

C #, Winform

Editar Cambiando la forma en que llena la matriz de coordenadas puede tener diferentes patrones - vea abajo

¿Te gusta este tipo de patrón?

Paisaje

Resumen

Prima:

Gritar Grito revuelto

Intercambio aleatorio exactamente una vez todos los píxeles en la mitad superior con todos los píxeles en la mitad inferior. Repita el mismo procedimiento para descifrar (bonificación).

Código

Scramble.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Imaging;
using System.IO;

namespace Palette
{
    public partial class Scramble : Form
    {
        public Scramble()
        {
            InitializeComponent();
        }

        public struct Coord
        {
            public int x, y;
        }

        private void Work(Bitmap srcb, Bitmap outb)
        {
            int w = srcb.Width, h = srcb.Height;
            Coord[] coord = new Coord[w * h];

            FastBitmap fsb = new FastBitmap(srcb);
            FastBitmap fob = new FastBitmap(outb);
            fsb.LockImage();
            fob.LockImage();
            ulong seed = 0;
            int numpix = 0;
            for (int y = 0; y < h; y++)
                for (int x = 0; x < w; numpix++, x++)
                {
                    coord[numpix].x = x;
                    coord[numpix].y = y;
                    uint color = fsb.GetPixel(x, y);
                    seed += color;
                    fob.SetPixel(x, y, color);
                }
            fsb.UnlockImage();
            fob.UnlockImage();
            pbOutput.Refresh();
            Application.DoEvents();

            int half = numpix / 2;
            int limit = half;
            XorShift rng = new XorShift(seed);
            progressBar.Visible = true;
            progressBar.Maximum = limit;

            fob.LockImage();
            while (limit > 0)
            {
                int p = (int)(rng.next() % (uint)limit);
                int q = (int)(rng.next() % (uint)limit);
                uint color = fob.GetPixel(coord[p].x, coord[p].y); 
                fob.SetPixel(coord[p].x, coord[p].y, fob.GetPixel(coord[half+q].x, coord[half+q].y)); 
                fob.SetPixel(coord[half+q].x, coord[half+q].y, color); 
                limit--;
                if (p < limit)
                {
                    coord[p]=coord[limit];
                }
                if (q < limit)
                {
                    coord[half+q]=coord[half+limit];
                }
                if ((limit & 0xfff) == 0)
                {
                    progressBar.Value = limit;
                    fob.UnlockImage();
                    pbOutput.Refresh();
                    fob.LockImage();
                }
            }
            fob.UnlockImage();
            pbOutput.Refresh();
            progressBar.Visible = false; 
        }

        void DupImage(PictureBox s, PictureBox d)
        {
            if (d.Image != null)
                d.Image.Dispose();
            d.Image = new Bitmap(s.Image.Width, s.Image.Height);  
        }

        void GetImagePB(PictureBox pb, string file)
        {
            Bitmap bms = new Bitmap(file, false);
            Bitmap bmp = bms.Clone(new Rectangle(0, 0, bms.Width, bms.Height), PixelFormat.Format32bppArgb);
            bms.Dispose(); 
            if (pb.Image != null)
                pb.Image.Dispose();
            pb.Image = bmp;
        }

        private void btnOpen_Click(object sender, EventArgs e)
        {
            OpenFileDialog openFileDialog = new OpenFileDialog();

            openFileDialog.InitialDirectory = "c:\\temp\\";
            openFileDialog.Filter = "Image Files(*.BMP;*.JPG;*.PNG)|*.BMP;*.JPG;*.PNG|All files (*.*)|*.*";
            openFileDialog.FilterIndex = 1;
            openFileDialog.RestoreDirectory = true;

            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                try
                {
                    string file = openFileDialog.FileName;
                    GetImagePB(pbInput, file);
                    pbInput.Tag = file;
                    DupImage(pbInput, pbOutput);
                    Work(pbInput.Image as Bitmap, pbOutput.Image as Bitmap);
                    file = Path.GetDirectoryName(file) + Path.DirectorySeparatorChar + Path.GetFileNameWithoutExtension(file) + ".scr.png";
                    pbOutput.Image.Save(file);
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Error: Could not read file from disk. Original error: " + ex.Message);
                }

            }
        }
    }

    //Adapted from Visual C# Kicks - http://www.vcskicks.com/
    unsafe public class FastBitmap
    {
        private Bitmap workingBitmap = null;
        private int width = 0;
        private BitmapData bitmapData = null;
        private Byte* pBase = null;

        public FastBitmap(Bitmap inputBitmap)
        {
            workingBitmap = inputBitmap;
        }

        public BitmapData LockImage()
        {
            Rectangle bounds = new Rectangle(Point.Empty, workingBitmap.Size);

            width = (int)(bounds.Width * 4 + 3) & ~3;

            //Lock Image
            bitmapData = workingBitmap.LockBits(bounds, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
            pBase = (Byte*)bitmapData.Scan0.ToPointer();
            return bitmapData;
        }

        private uint* pixelData = null;

        public uint GetPixel(int x, int y)
        {
            pixelData = (uint*)(pBase + y * width + x * 4);
            return *pixelData;
        }

        public uint GetNextPixel()
        {
            return *++pixelData;
        }

        public void GetPixelArray(int x, int y, uint[] Values, int offset, int count)
        {
            pixelData = (uint*)(pBase + y * width + x * 4);
            while (count-- > 0)
            {
                Values[offset++] = *pixelData++;
            }
        }

        public void SetPixel(int x, int y, uint color)
        {
            pixelData = (uint*)(pBase + y * width + x * 4);
            *pixelData = color;
        }

        public void SetNextPixel(uint color)
        {
            *++pixelData = color;
        }

        public void UnlockImage()
        {
            workingBitmap.UnlockBits(bitmapData);
            bitmapData = null;
            pBase = null;
        }
    }

    public class XorShift
    {
        private ulong x; /* The state must be seeded with a nonzero value. */

        public XorShift(ulong seed)
        {
            x = seed;
        }

        public ulong next()
        {
            x ^= x >> 12; // a
            x ^= x << 25; // b
            x ^= x >> 27; // c
            return x * 2685821657736338717L;
        }
    }
} 

Scramble.designer.cs

namespace Palette
{
    partial class Scramble
    {
        private System.ComponentModel.IContainer components = null;

        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        private void InitializeComponent()
        {
            this.panel = new System.Windows.Forms.FlowLayoutPanel();
            this.pbInput = new System.Windows.Forms.PictureBox();
            this.pbOutput = new System.Windows.Forms.PictureBox();
            this.progressBar = new System.Windows.Forms.ProgressBar();
            this.btnOpen = new System.Windows.Forms.Button();
            this.panel.SuspendLayout();
            ((System.ComponentModel.ISupportInitialize)(this.pbInput)).BeginInit();
            ((System.ComponentModel.ISupportInitialize)(this.pbOutput)).BeginInit();
            this.SuspendLayout();
            // 
            // panel
            // 
            this.panel.AutoScroll = true;
            this.panel.AutoSize = true;
            this.panel.Controls.Add(this.pbInput);
            this.panel.Controls.Add(this.pbOutput);
            this.panel.Dock = System.Windows.Forms.DockStyle.Top;
            this.panel.Location = new System.Drawing.Point(0, 0);
            this.panel.Name = "panel";
            this.panel.Size = new System.Drawing.Size(748, 306);
            this.panel.TabIndex = 3;
            // 
            // pbInput
            // 
            this.pbInput.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
            this.pbInput.Location = new System.Drawing.Point(3, 3);
            this.pbInput.MinimumSize = new System.Drawing.Size(100, 100);
            this.pbInput.Name = "pbInput";
            this.pbInput.Size = new System.Drawing.Size(100, 300);
            this.pbInput.SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize;
            this.pbInput.TabIndex = 3;
            this.pbInput.TabStop = false;
            // 
            // pbOutput
            // 
            this.pbOutput.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
            this.pbOutput.Location = new System.Drawing.Point(109, 3);
            this.pbOutput.MinimumSize = new System.Drawing.Size(100, 100);
            this.pbOutput.Name = "pbOutput";
            this.pbOutput.Size = new System.Drawing.Size(100, 300);
            this.pbOutput.SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize;
            this.pbOutput.TabIndex = 4;
            this.pbOutput.TabStop = false;
            // 
            // progressBar
            // 
            this.progressBar.Dock = System.Windows.Forms.DockStyle.Bottom;
            this.progressBar.Location = new System.Drawing.Point(0, 465);
            this.progressBar.Name = "progressBar";
            this.progressBar.Size = new System.Drawing.Size(748, 16);
            this.progressBar.TabIndex = 5;
            // 
            // btnOpen
            // 
            this.btnOpen.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
            this.btnOpen.Location = new System.Drawing.Point(12, 429);
            this.btnOpen.Name = "btnOpen";
            this.btnOpen.Size = new System.Drawing.Size(53, 30);
            this.btnOpen.TabIndex = 6;
            this.btnOpen.Text = "Start";
            this.btnOpen.UseVisualStyleBackColor = true;
            this.btnOpen.Click += new System.EventHandler(this.btnOpen_Click);
            // 
            // Scramble
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.BackColor = System.Drawing.SystemColors.ControlDark;
            this.ClientSize = new System.Drawing.Size(748, 481);
            this.Controls.Add(this.btnOpen);
            this.Controls.Add(this.progressBar);
            this.Controls.Add(this.panel);
            this.Name = "Scramble";
            this.Text = "Form1";
            this.panel.ResumeLayout(false);
            this.panel.PerformLayout();
            ((System.ComponentModel.ISupportInitialize)(this.pbInput)).EndInit();
            ((System.ComponentModel.ISupportInitialize)(this.pbOutput)).EndInit();
            this.ResumeLayout(false);
            this.PerformLayout();

        }


        private System.Windows.Forms.FlowLayoutPanel panel;
        private System.Windows.Forms.PictureBox pbOutput;
        private System.Windows.Forms.ProgressBar progressBar;
        private System.Windows.Forms.PictureBox pbInput;
        private System.Windows.Forms.Button btnOpen;
    }
}

Program.cs

using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace Palette
{
  static class Program
  {
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Scramble());
    }
  }
}

Marque 'Código inseguro' en la propiedad del proyecto para compilar.

Patrón complejo

Lucha

Cambie la primera parte de la función de trabajo, hasta Application.DoEvents:

        int w = srcb.Width, h = srcb.Height;
        string Msg = "Scramble";

        Graphics gr = Graphics.FromImage(outb);

        Font f = new Font("Arial", 100, FontStyle.Bold);
        var size = gr.MeasureString(Msg, f);
        f = new Font("Arial", w / size.Width * 110, FontStyle.Bold);
        size = gr.MeasureString(Msg, f);
        gr.DrawString(Msg, f, new SolidBrush(Color.White), (w - size.Width) / 2, (h - size.Height) / 2);

        gr.Dispose();

        Coord[] coord = new Coord[w * h];
        FastBitmap fsb = new FastBitmap(srcb);
        FastBitmap fob = new FastBitmap(outb);
        fsb.LockImage();
        fob.LockImage();
        ulong seed = 1;
        int numpix = h * w;
        int c1 = 0, c2 = numpix;
        int y2 = h / 2;

        int p2 = numpix/2;

        for (int p = 0; p < p2; p++)
        {
            for (int s = 1; s > -2; s -= 2)
            {
                int y = (p2+s*p) / w;
                int x = (p2+s*p) % w;

                uint d = fob.GetPixel(x, y);
                if (d != 0)
                {
                    c2--;
                    coord[c2].x = x;
                    coord[c2].y = y;
                }
                else
                {
                    coord[c1].x = x;
                    coord[c1].y = y;
                    c1++;
                }
                fob.SetPixel(x, y, fsb.GetPixel(x, y));
            }
        }
        fsb.UnlockImage();
        fob.UnlockImage();
        pbOutput.Refresh();
        Application.DoEvents();
edc65
fuente
1
Interesante) Me pregunto si con un enfoque similar es posible hacer patrones más complicados en la salida.
Somnium
1
Buena idea: ¿qué le sucede a la línea media cuando hay un número impar de líneas?
flawr
1
@flawr la división es por píxel. Si hay un número impar de píxeles, el último no se modifica. Si hay un número impar de filas, la mitad izquierda de la fila central es 'lado superior' y la mitad derecha es 'lado inferior'.
edc65
1
@ user2992539 Creo que puedes subdividir más, incluso el tablero de ajedrez. Con más subdivisiones, la imagen es más reconocible.
edc65
77
¡Como su versión "Scramble"!)
Somnium
43

C, desenfoque arbitrario, fácilmente reversible

Tarde a la fiesta. Aquí está mi entrada!

Este método hace un desenfoque de aleatorización. Yo lo llamo scramblur . Es extremadamente simple En un bucle, elige un píxel aleatorio y luego lo intercambia con un píxel cercano elegido aleatoriamente en un modelo de lienzo toroidal. Usted especifica la distancia máxima que define lo que significa "píxel cercano" (1 significa elegir siempre un píxel adyacente), el número de iteraciones y, opcionalmente, un número inicial aleatorio. Cuanto mayor sea la distancia máxima y mayor sea el número de iteraciones, más borroso será el resultado.

Es reversible especificando un número negativo de iteraciones (esto es simplemente una conveniencia de interfaz de línea de comandos; en realidad no existe tal cosa como iteraciones negativas). Internamente, utiliza un LCPRNG de 64 bits personalizado (generador de números pseudoaleatorios congruenciales lineales) y genera previamente un bloque de valores. La tabla permite recorrer el bloque hacia adelante o hacia atrás para codificar o descifrar, respectivamente.

Manifestación

Para las dos primeras imágenes, a medida que se desplaza hacia abajo, cada imagen se desenfoca utilizando un desplazamiento máximo más alto: la más alta es la imagen original (por ejemplo, desplazamiento de 0 píxeles), seguida de 1, 2, 4, 8, 16, 32, 64 , 128 y finalmente 256. El recuento de iteraciones es 10⁶ = 1,000,000 para todas las imágenes a continuación.

Para las segundas dos imágenes, cada imagen se difumina usando un desplazamiento progresivamente más bajo , por ejemplo, más borroso a menos borroso, desde un desplazamiento máximo de 256 hasta 0. ¡Disfruta!

Paisaje Resumen

Y para las siguientes dos imágenes, puede ver las progresiones a tamaño completo aquí y aquí :

Hacerse malo Los simpsons

Código

Pirateé esto juntos en aproximadamente una hora mientras me despertaba esta mañana y casi no contiene documentación. Podría volver en unos días y agregar más documentación más tarde si la gente lo solicita.

//=============================================================================
// SCRAMBLUR
//
// This program is a image-processing competition entry which scrambles or
// descrambles an image based on a pseudorandom process.  For more details,
// information, see:
//
//    http://codegolf.stackexchange.com/questions/35005
//
// It is assumed that you have the NETPBM package of image-processing tools
// installed on your system.  This can be obtained from:
//
//    http://netpbm.sourceforge.net/
//
// or by using your system's package manager, e.g., yum, apt-get, port, etc.
//
// Input to the program is a 24-bit PNM image (type "P6").  Output is same.
// Example command-line invocation:
//
// pngtopnm original.png  | scramblur 100  1000000 | pnmtopng >scrambled.png
// pngtopnm scrambled.png | scramblur 100 -1000000 | pnmtopng >recovered.png
//
//
// Todd S. Lehman, July 2014

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>

typedef uint8_t uint8;
typedef uint64_t uint64;

//-----------------------------------------------------------------------------
// PIXEL STRUCTURE

#pragma pack(push, 1)
typedef struct
{
  uint8 r, g, b;     // Red, green, and blue color components
}
Pixel;
#pragma pack(pop)

//-----------------------------------------------------------------------------
// IMAGE STRUCTURE

typedef struct
{
  int width;          // Width of image in pixels
  int height;         // Height of image in pixels
  int pixel_count;    // Total number of pixels in image (e.g., width * height)
  int maxval;         // Maximum pixel component value (e.g., 255)
  Pixel *data;        // One-dimensional array of pixels
}
Image;

//-----------------------------------------------------------------------------
// 64-BIT LCG TABLE

static const long lcg64_table_length = 1000000;  // 10⁶ entries => 8 Megabytes

static uint64 lcg64_table[lcg64_table_length];

//-----------------------------------------------------------------------------
// GET 64-BIT LCG VALUE FROM TABLE

uint64 lcg64_get(long const iteration)
{
  return lcg64_table[iteration % lcg64_table_length];
}

//-----------------------------------------------------------------------------
// INITIALIZE 64-BIT LCG TABLE

void lcg64_init(uint64 const seed)
{
  uint64 x = seed;
  for (long iteration = 0; iteration < lcg64_table_length; iteration++)
  {
    uint64 const a = UINT64_C(6364136223846793005);
    uint64 const c = UINT64_C(1442695040888963407);
    x = (x * a) + c;
    lcg64_table[iteration] = x;
  }
}

//-----------------------------------------------------------------------------
// READ BINARY PNM IMAGE

Image image_read(FILE *const file)
{
  Image image = { .data = NULL };

  char *line = NULL;
  size_t linecap = 0;

  // Read image type.  (Currently only P6 is supported here.)
  if (getline(&line, &linecap, file) < 0) goto failure;
  if (strcmp(line, "P6\n") != 0) goto failure;

  // Read width and height of image in pixels.
  {
    if (getline(&line, &linecap, file) < 0) goto failure;
    char *pwidth = &line[0];
    char *pheight = strchr(line, ' ');
    if (pheight != NULL) pheight++; else goto failure;
    image.width = atoi(pwidth);
    image.height = atoi(pheight);
    image.pixel_count = image.width * image.height;
  }

  // Read maximum color value.  (Currently only 255 is supported here.)
  {
    if (getline(&line, &linecap, file) < 0) goto failure;
    image.maxval = atoi(line);
    if (image.maxval != 255)
      goto failure;
  }

  // Allocate image buffer and read image data.
  if (!(image.data = calloc(image.pixel_count, sizeof(Pixel))))
    goto failure;

  if (fread(image.data, sizeof(Pixel), image.pixel_count, file) !=
      image.pixel_count)
    goto failure;

success:
  free(line);
  return image;

failure:
  free(line);
  free(image.data); image.data = NULL;
  return image;
}

//-----------------------------------------------------------------------------
// WRITE BINARY PNM IMAGE

void image_write(const Image image, FILE *const file)
{
  printf("P6\n");
  printf("%d %d\n", image.width, image.height);
  printf("%d\n", image.maxval);
  (void)fwrite(image.data, sizeof(Pixel), image.pixel_count, file);
}

//-----------------------------------------------------------------------------
// DISCARD IMAGE

void image_discard(Image image)
{
  free(image.data);
}

//-----------------------------------------------------------------------------
// SCRAMBLE OR UNSCRAMBLE IMAGE

void image_scramble(Image image,
                    int const max_delta,
                    long const iterations,
                    uint64 const lcg64_seed)
{
  if (max_delta == 0) return;

  int neighborhood1 = (2 * max_delta) + 1;
  int neighborhood2 = neighborhood1 * neighborhood1;

  lcg64_init(lcg64_seed);

  long iteration_start = (iterations >= 0)? 0 : -iterations;
  long iteration_end   = (iterations >= 0)? iterations : 0;
  long iteration_inc   = (iterations >= 0)? 1 : -1;

  for (long iteration = iteration_start;
       iteration != iteration_end;
       iteration += iteration_inc)
  {
    uint64 lcg64 = lcg64_get(iteration);

    // Choose random pixel.
    int pixel_index = (int)((lcg64 >> 0) % image.pixel_count);

    // Choose random pixel in the neighborhood.
    int d2 = (int)((lcg64 >> 8) % neighborhood2);
    int dx = (d2 % neighborhood1) - (neighborhood1 / 2);
    int dy = (d2 / neighborhood1) - (neighborhood1 / 2);
    int other_pixel_index = pixel_index + dx + (dy * image.width);
    while (other_pixel_index < 0)
      other_pixel_index += image.pixel_count;
    other_pixel_index %= image.pixel_count;

    // Swap pixels.
    Pixel t = image.data[pixel_index];
    image.data[pixel_index] = image.data[other_pixel_index];
    image.data[other_pixel_index] = t;
  }
}

//-----------------------------------------------------------------------------
int main(const int argc, char const *const argv[])
{
  int max_delta     = (argc > 1)? atoi(argv[1]) : 1;
  long iterations   = (argc > 2)? atol(argv[2]) : 1000000;
  uint64 lcg64_seed = (argc > 3)? (uint64)strtoull(argv[3], NULL, 10) : 0;

  Image image = image_read(stdin);
  if (!image.data) { fprintf(stderr, "Invalid input\n"), exit(1); }

  image_scramble(image, max_delta, iterations, lcg64_seed);

  image_write(image, stdout);

  image_discard(image);

  return 0;
}
Todd Lehman
fuente
44
Acabo de pasar esta respuesta, se ve increíble
Thomas
1
Esta respuesta es realmente alta. ¿Crees que podrías mover las imágenes adicionales (es decir, todo excepto las dos imágenes de prueba, totalmente borrosas) a una galería fuera del sitio?
Tim S.
@TimS. - ¡hecho! los redujo a miniaturas minúsculas.
Todd Lehman
42

Python 3.4

  • Bono 1: Auto inverso: la repetición restaura la imagen original.
  • Imagen clave opcional: la imagen original solo se puede restaurar utilizando la misma imagen clave nuevamente.
  • Bonus 2: Producción de patrón en la salida: la imagen clave se aproxima en los píxeles codificados.

Cuando se alcanza la bonificación 2, al usar una imagen clave adicional, la bonificación 1 no se pierde. El programa todavía es auto inverso, siempre que se ejecute con la misma imagen clave nuevamente.

Uso estándar

Imagen de prueba 1:

Imagen de prueba codificada 1

Imagen de prueba 2:

Imagen de prueba codificada 2

Ejecutar el programa con un solo archivo de imagen como argumento guarda un archivo de imagen con los píxeles revueltos uniformemente sobre toda la imagen. Ejecutarlo nuevamente con la salida codificada guarda un archivo de imagen con la codificación aplicada nuevamente, lo que restaura el original ya que el proceso de codificación es inverso.

El proceso de aleatorización es inverso a sí mismo porque la lista de todos los píxeles se divide en 2 ciclos, de modo que cada píxel se intercambia con uno y solo otro píxel. Ejecutarlo por segunda vez intercambia cada píxel con el píxel con el que se intercambió por primera vez, volviendo todo a cómo comenzó. Si hay un número impar de píxeles, habrá uno que no se mueva.

Gracias a la respuesta de mfvonh como la primera en sugerir 2 ciclos.

Uso con una imagen clave

Aleatorización de la imagen de prueba 1 con la imagen de prueba 2 como imagen clave

Prueba de codificación 1 con prueba 2

Aleatorización de la imagen de prueba 2 con la imagen de prueba 1 como imagen clave

Prueba de codificación 2 con prueba 1

Ejecutar el programa con un segundo argumento de archivo de imagen (la imagen clave) divide la imagen original en regiones basadas en la imagen clave. Cada una de estas regiones se divide en 2 ciclos por separado, de modo que toda la codificación ocurre dentro de las regiones, y los píxeles no se mueven de una región a otra. Esto extiende los píxeles sobre cada región y, por lo tanto, las regiones se convierten en un color moteado uniforme, pero con un color promedio ligeramente diferente para cada región. Esto proporciona una aproximación muy aproximada de la imagen clave, en los colores incorrectos.

La ejecución de nuevo intercambia los mismos pares de píxeles en cada región, por lo que cada región se restaura a su estado original y la imagen en su conjunto vuelve a aparecer.

Gracias a la respuesta de edc65 como la primera en sugerir dividir la imagen en regiones. Quería ampliar esto para usar regiones arbitrarias, pero el enfoque de intercambiar todo en la región 1 con todo en la región 2 significaba que las regiones tenían que ser del mismo tamaño. Mi solución es mantener las regiones aisladas unas de otras, y simplemente mezclar cada región en sí misma. Como las regiones ya no necesitan tener un tamaño similar, se vuelve más simple aplicar regiones con formas arbitrarias.

Código

import os.path
from PIL import Image   # Uses Pillow, a fork of PIL for Python 3
from random import randrange, seed


def scramble(input_image_filename, key_image_filename=None,
             number_of_regions=16777216):
    input_image_path = os.path.abspath(input_image_filename)
    input_image = Image.open(input_image_path)
    if input_image.size == (1, 1):
        raise ValueError("input image must contain more than 1 pixel")
    number_of_regions = min(int(number_of_regions),
                            number_of_colours(input_image))
    if key_image_filename:
        key_image_path = os.path.abspath(key_image_filename)
        key_image = Image.open(key_image_path)
    else:
        key_image = None
        number_of_regions = 1
    region_lists = create_region_lists(input_image, key_image,
                                       number_of_regions)
    seed(0)
    shuffle(region_lists)
    output_image = swap_pixels(input_image, region_lists)
    save_output_image(output_image, input_image_path)


def number_of_colours(image):
    return len(set(list(image.getdata())))


def create_region_lists(input_image, key_image, number_of_regions):
    template = create_template(input_image, key_image, number_of_regions)
    number_of_regions_created = len(set(template))
    region_lists = [[] for i in range(number_of_regions_created)]
    for i in range(len(template)):
        region = template[i]
        region_lists[region].append(i)
    odd_region_lists = [region_list for region_list in region_lists
                        if len(region_list) % 2]
    for i in range(len(odd_region_lists) - 1):
        odd_region_lists[i].append(odd_region_lists[i + 1].pop())
    return region_lists


def create_template(input_image, key_image, number_of_regions):
    if number_of_regions == 1:
        width, height = input_image.size
        return [0] * (width * height)
    else:
        resized_key_image = key_image.resize(input_image.size, Image.NEAREST)
        pixels = list(resized_key_image.getdata())
        pixel_measures = [measure(pixel) for pixel in pixels]
        distinct_values = list(set(pixel_measures))
        number_of_distinct_values = len(distinct_values)
        number_of_regions_created = min(number_of_regions,
                                        number_of_distinct_values)
        sorted_distinct_values = sorted(distinct_values)
        while True:
            values_per_region = (number_of_distinct_values /
                                 number_of_regions_created)
            value_to_region = {sorted_distinct_values[i]:
                               int(i // values_per_region)
                               for i in range(len(sorted_distinct_values))}
            pixel_regions = [value_to_region[pixel_measure]
                             for pixel_measure in pixel_measures]
            if no_small_pixel_regions(pixel_regions,
                                      number_of_regions_created):
                break
            else:
                number_of_regions_created //= 2
        return pixel_regions


def no_small_pixel_regions(pixel_regions, number_of_regions_created):
    counts = [0 for i in range(number_of_regions_created)]
    for value in pixel_regions:
        counts[value] += 1
    if all(counts[i] >= 256 for i in range(number_of_regions_created)):
        return True


def shuffle(region_lists):
    for region_list in region_lists:
        length = len(region_list)
        for i in range(length):
            j = randrange(length)
            region_list[i], region_list[j] = region_list[j], region_list[i]


def measure(pixel):
    '''Return a single value roughly measuring the brightness.

    Not intended as an accurate measure, simply uses primes to prevent two
    different colours from having the same measure, so that an image with
    different colours of similar brightness will still be divided into
    regions.
    '''
    if type(pixel) is int:
        return pixel
    else:
        r, g, b = pixel[:3]
        return r * 2999 + g * 5869 + b * 1151


def swap_pixels(input_image, region_lists):
    pixels = list(input_image.getdata())
    for region in region_lists:
        for i in range(0, len(region) - 1, 2):
            pixels[region[i]], pixels[region[i+1]] = (pixels[region[i+1]],
                                                      pixels[region[i]])
    scrambled_image = Image.new(input_image.mode, input_image.size)
    scrambled_image.putdata(pixels)
    return scrambled_image


def save_output_image(output_image, full_path):
    head, tail = os.path.split(full_path)
    if tail[:10] == 'scrambled_':
        augmented_tail = 'rescued_' + tail[10:]
    else:
        augmented_tail = 'scrambled_' + tail
    save_filename = os.path.join(head, augmented_tail)
    output_image.save(save_filename)


if __name__ == '__main__':
    import sys
    arguments = sys.argv[1:]
    if arguments:
        scramble(*arguments[:3])
    else:
        print('\n'
              'Arguments:\n'
              '    input image          (required)\n'
              '    key image            (optional, default None)\n'
              '    number of regions    '
              '(optional maximum - will be as high as practical otherwise)\n')

Grabación de imagen JPEG

Los archivos .jpg se procesan muy rápidamente, pero a costa de correr demasiado caliente. Esto deja una imagen posterior quemada cuando se restaura el original:

jpg burn

Pero en serio, un formato con pérdida dará como resultado que algunos de los colores de los píxeles se cambien ligeramente, lo que en sí mismo invalida la salida. Cuando se usa una imagen clave y la combinación aleatoria de píxeles se restringe a regiones, toda la distorsión se mantiene dentro de la región en la que sucedió, y luego se extiende uniformemente sobre esa región cuando se restaura la imagen. La diferencia en la distorsión promedio entre regiones deja una diferencia visible entre ellas, por lo que las regiones utilizadas en el proceso de codificación aún son visibles en la imagen restaurada.

La conversión a .png (o cualquier formato sin pérdidas) antes de codificar garantiza que la imagen sin codificar sea idéntica a la original sin quemaduras ni distorsiones:

png sin quemar

Pequeños detalles

  • Se impone un tamaño mínimo de 256 píxeles en las regiones. Si se permitiera que la imagen se dividiera en regiones que son demasiado pequeñas, la imagen original aún sería parcialmente visible después de la codificación.
  • Si hay más de una región con un número impar de píxeles, un píxel de la segunda región se reasigna a la primera, y así sucesivamente. Esto significa que solo puede haber una región con un número impar de píxeles, por lo que solo un píxel permanecerá sin codificar.
  • Hay un tercer argumento opcional que restringe el número de regiones. Establecer esto en 2, por ejemplo, dará dos imágenes codificadas en tonos. Esto puede verse mejor o peor dependiendo de las imágenes involucradas. Si se especifica un número aquí, la imagen solo se puede restaurar utilizando el mismo número nuevamente.
  • El número de colores distintos en la imagen original también limita el número de regiones. Si la imagen original es de dos tonos, independientemente de la imagen clave o del tercer argumento, solo puede haber un máximo de 2 regiones.
trichoplax
fuente
2
+1 Aplausos! Pensé vagamente en esto, pero me pareció demasiado difícil de implementar.
edc65
1
Esto es brillante. Tengo una entrada enviada, pero me gusta más la tuya debido a la función de imagen clave.
Todd Lehman
Me gustaría saber cómo se ven estas dos imágenes entre sí: lardlad.com/assets/wallpaper/simpsons1920.jpg y blogs.nd.edu/oblation/files/2013/09/BreakingBad.jpg (reducido a 720x450 o lo que tenga sentido y, por supuesto, preconvertido a PNG para evitar la grabación JPEG).
Todd Lehman
2
@ToddLehman mi algoritmo está limitado por la necesidad de ser su propio inverso. Si desea ver algunos enfoques realmente interesantes para mezclar una imagen para que se parezca a otra, debe mirar American Gothic en la paleta de Mona Lisa . Algunos de esos programas harían cosas increíbles con las imágenes que mencionas.
trichoplax
2
La característica de imagen clave pone esta cabeza y hombros por encima del resto.
Jack Aidley
33

Aquí hay una transformación no aleatoria para un cambio

  1. Coloque todas las columnas pares a la izquierda y todas las columnas impares a la derecha.
  2. repetir nxtiempos
  3. hacer lo mismo por filas nyveces

La transformación es casi autoinversa, repitiendo la transformación un total de size_xveces (en dirección x) devuelve la imagen original. No descubrí las matemáticas exactas, pero el uso de múltiplos enteros int(log_2(size_x))produce la mejor combinación con las imágenes fantasma más pequeñas

montañas arrastradas ingrese la descripción de la imagen aquí

from numpy import *
from pylab import imread, imsave

def imshuffle(im, nx=0, ny=0):
    for i in range(nx):
        im = concatenate((im[:,0::2], im[:,1::2]), axis=1)
    for i in range(ny):
        im = concatenate((im[0::2,:], im[1::2,:]), axis=0)
    return im

im1 = imread('circles.png')
im2 = imread('mountain.jpg')

imsave('s_circles.png', imshuffle(im1, 7,7))
imsave('s_mountain.jpg', imshuffle(im2, 8,9))

Así es como se ven los primeros pasos 20 iteraciones (nx = ny, tenga en cuenta el efecto de diferentes resoluciones) ingrese la descripción de la imagen aquí

DenDenDo
fuente
77
Ese es un algoritmo realmente genial. Y debería obtener una bonificación por usar la imagen de Lena Söderberg. :)
Todd Lehman
Siempre vota a Lena
24

Mathematica

Esto es bastante sencillo. Elijo 5 * nPixelspares de coordenadas aleatorias y cambio esos dos píxeles (lo que oscurece completamente la imagen). Para descifrarlo hago lo mismo a la inversa. Por supuesto, necesito sembrar el PRNG para obtener los mismos pares de coordenadas en ambos pasos.

scramble[image_] := Module[
   {data, h, w, temp},
   data = ImageData@image;
   {h, w} = Most@Dimensions@data;
   SeedRandom[42];
   (
      temp = data[[#[[1]], #[[2]]]];
      data[[#[[1]], #[[2]]]] = data[[#2[[1]], #2[[2]]]];
      data[[#2[[1]], #2[[2]]]] = temp;
      ) & @@@
    Partition[
     Transpose@{RandomInteger[h - 1, 10*h*w] + 1, 
       RandomInteger[w - 1, 10*h*w] + 1}, 2];
   Image@data
   ];
unscramble[image_] := Module[
   {data, h, w, temp},
   data = ImageData@image;
   {h, w} = Most@Dimensions@data;
   SeedRandom[42];
   (
      temp = data[[#[[1]], #[[2]]]];
      data[[#[[1]], #[[2]]]] = data[[#2[[1]], #2[[2]]]];
      data[[#2[[1]], #2[[2]]]] = temp;
      ) & @@@
    Reverse@
     Partition[
      Transpose@{RandomInteger[h - 1, 10*h*w] + 1, 
        RandomInteger[w - 1, 10*h*w] + 1}, 2];
   Image@data
   ];

La única diferencia entre las dos funciones está Reverse@en unscramble. Ambas funciones toman un objeto de imagen real. Puede usarlos de la siguiente manera:

in = Import["D:\\Development\\CodeGolf\\image-scrambler\\circles.png"]
scr = scramble[im]
out = unscramble[scr]

outy inson idénticos Así es como se scrve:

ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

Martin Ender
fuente
44
¡Excelente! El único problema es que es más seguro hacer PRNG usted mismo, porque si después de algún tiempo Mathematica piensa cambiar el algoritmo PRNG, ¡esto no decodificará imágenes codificadas antiguas!
Somnium
1
Agradable. Debería poder lograr el mismo resultado con Permute y FindPermutation.
DavidC
No estoy seguro de entender. Puede ingresar la permutación precisa que desea como una lista de ciclos.
DavidC
@DavidCarraher Hm, interesante. ¿No tendría que recordar la permutación original para usar FindPermutation?
Martin Ender
O tal vez algo como {c, a, b}[[{2, 3, 1}]]se puede utilizar?
Somnium
22

C # (+ Bonificación por algoritmo simétrico)

Esto funciona encontrando un xtal que x^2 == 1 mod (number of pixels in image), y luego multiplicando el índice de cada píxel por xpara encontrar su nueva ubicación. Esto le permite utilizar exactamente el mismo algoritmo para codificar y descifrar una imagen.

using System.Drawing;
using System.IO;
using System.Numerics;

namespace RearrangePixels
{
    class Program
    {
        static void Main(string[] args)
        {
            foreach (var arg in args)
                ScrambleUnscramble(arg);
        }

        static void ScrambleUnscramble(string fileName)
        {
            using (var origImage = new Bitmap(fileName))
            using (var newImage = new Bitmap(origImage))
            {
                BigInteger totalPixels = origImage.Width * origImage.Height;
                BigInteger modSquare = GetSquareRootOf1(totalPixels);
                for (var x = 0; x < origImage.Width; x++)
                {
                    for (var y = 0; y < origImage.Height; y++)
                    {
                        var newNum = modSquare * GetPixelNumber(new Point(x, y), origImage.Size) % totalPixels;
                        var newPoint = GetPoint(newNum, origImage.Size);
                        newImage.SetPixel(newPoint.X, newPoint.Y, origImage.GetPixel(x, y));
                    }
                }
                newImage.Save("scrambled-" + Path.GetFileName(fileName));
            }
        }

        static BigInteger GetPixelNumber(Point point, Size totalSize)
        {
            return totalSize.Width * point.Y + point.X;
        }

        static Point GetPoint(BigInteger pixelNumber, Size totalSize)
        {
            return new Point((int)(pixelNumber % totalSize.Width), (int)(pixelNumber / totalSize.Width));
        }

        static BigInteger GetSquareRootOf1(BigInteger modulo)
        {
            for (var i = (BigInteger)2; i < modulo - 1; i++)
            {
                if ((i * i) % modulo == 1)
                    return i;
            }
            return modulo - 1;
        }
    }
}

primera imagen de prueba, revuelta

segunda imagen de prueba, revuelta

Tim S.
fuente
1
Uno inteligente) ¿Habrá siempre una solución para esa ecuación de congruencia?
Somnium
1
@ user2992539 Siempre habrá soluciones triviales 1(imagen original) e modulo-1(imagen invertida / invertida). La mayoría de los números tienen soluciones no triviales, pero parece que hay algunas excepciones . (relacionado con la factorización prima de modulo)
Tim S.
Según tengo entendido, las soluciones triviales conducen a una imagen similar a la entrada uno.
Somnium
Correcto: 1muestra la imagen original y muestra,-1 por ejemplo, imgur.com/EiE6VW2
Tim S.
19

C #, autoinverso, sin aleatoriedad

Si la imagen original tiene dimensiones que son potencias de dos, entonces cada fila y columna se intercambia con la fila y columna que tiene el patrón de bits invertido, por ejemplo, para una imagen de ancho 256, la fila 0xB4 se intercambia con la fila 0x2D. Las imágenes de otros tamaños se dividen en rectángulos con lados de potencias de 2.

namespace CodeGolf
{
    class Program
    {
        static void Main(string[] args)
        {
            foreach (var arg in args)
                Scramble(arg);
        }

        static void Scramble(string fileName)
        {
            using (var origImage = new System.Drawing.Bitmap(fileName))
            using (var tmpImage = new System.Drawing.Bitmap(origImage))
            {
                {
                    int x = origImage.Width;
                    while (x > 0) {
                       int xbit = x & -x;
                        do {
                            x--;
                            var xalt = BitReverse(x, xbit);
                            for (int y = 0; y < origImage.Height; y++)
                                tmpImage.SetPixel(xalt, y, origImage.GetPixel(x, y));
                        } while ((x & (xbit - 1)) != 0);
                    }
                }
                {
                    int y = origImage.Height;
                    while (y > 0) {
                        int ybit = y & -y;
                        do {
                            y--;
                            var yalt = BitReverse(y, ybit);
                            for (int x = 0; x < origImage.Width; x++)
                                origImage.SetPixel(x, yalt, tmpImage.GetPixel(x, y));
                        } while ((y & (ybit - 1)) != 0);
                    } 
                }
                origImage.Save(System.IO.Path.GetFileNameWithoutExtension(fileName) + "-scrambled.png");
            }
        }

        static int BitReverse(int n, int bit)
        {
            if (bit < 4)
                return n;
            int r = n & ~(bit - 1);
            int tmp = 1;
            while (bit > 1) {
                bit >>= 1;
                if ((n & bit) != 0)
                    r |= tmp;
                tmp <<= 1;
            }
            return r;
        }
    }
}

Primera imagen

Revuelto primera imagen

Segunda imagen:

Revuelto segunda imagen

Neil
fuente
2
Me gusta la salida "a cuadros" en este caso.
Brian Rogers
14

C#

El mismo método para codificar y descifrar. Agradecería sugerencias para mejorar esto.

using System;
using System.Drawing;
using System.Linq;

public class Program
{
    public static Bitmap Scramble(Bitmap bmp)
    {
        var res = new Bitmap(bmp);
        var r = new Random(1);

        // Making lists of even and odd numbers and shuffling them
        // They contain numbers between 0 and picture.Width (or picture.Height)
        var rX = Enumerable.Range(0, bmp.Width / 2).Select(x => x * 2).OrderBy(x => r.Next()).ToList();
        var rrX = rX.Select(x => x + 1).OrderBy(x => r.Next()).ToList();
        var rY = Enumerable.Range(0, bmp.Height / 2).Select(x => x * 2).OrderBy(x => r.Next()).ToList();
        var rrY = rY.Select(x => x + 1).OrderBy(x => r.Next()).ToList();

        for (int y = 0; y < bmp.Height; y++)
        {
            for (int x = 0; x < rX.Count; x++)
            {
                // Swapping pixels in a row using lists rX and rrX
                res.SetPixel(rrX[x], y, bmp.GetPixel(rX[x], y));
                res.SetPixel(rX[x], y, bmp.GetPixel(rrX[x], y));
            }
        }
        for (int x = 0; x < bmp.Width; x++)
        {
            for (int y = 0; y < rY.Count; y++)
            {
                // Swapping pixels in a column using sets rY and rrY
                var px = res.GetPixel(x, rrY[y]);
                res.SetPixel(x, rrY[y], res.GetPixel(x, rY[y]));
                res.SetPixel(x, rY[y], px);
            }
        }

        return res;
    }
}

Resultados de resultados en cuadros psicodélicos El primero Segundo

Ray Poward
fuente
Es bueno que esto tenga un patrón de rayas)
Somnium
1
¿Puedes intercambiar las 2 imágenes? En la pregunta, la imagen de las montañas es lo primero.
AL
1
¿Podría incluir una breve explicación del algoritmo?
trichoplax
14

Python 2 (autoinverso, sin aleatoriedad, sensible al contexto)

Esto no ganará ningún premio por "menos reconocible", pero tal vez pueda calificar como "interesante". :-)

Quería hacer algo sensible al contexto, donde la codificación de los píxeles realmente depende de la imagen misma.

La idea es bastante simple: ordenar todos los píxeles de acuerdo con algún valor arbitrario derivado del color del píxel y luego intercambiar las posiciones del primer píxel en esa lista con el último, el segundo con el segundo al último, y así sucesivamente.

Desafortunadamente, en este enfoque simple hay un problema con los píxeles del mismo color, por lo que para que sea autoinverso, mi programa se volvió un poco más complicado ...

from PIL import Image

img = Image.open('1.png', 'r')
pixels = img.load()
size_x, size_y = img.size

def f(colour):
    r,g,b = colour[:3]
    return (abs(r-128)+abs(g-128)+abs(b-128))//128

pixel_list = [(x,y,f(pixels[x,y])) for x in xrange(size_x) for y in xrange(size_y)]
pixel_list.sort(key=lambda x: x[2])
print "sorted"

colours = {}
for p in pixel_list:
    if p[2] in colours:
        colours[p[2]] += 1
    else:
        colours[p[2]] = 1
print "counted"

for n in set(colours.itervalues()):
    pixel_group = [p for p in pixel_list if colours[p[2]]==n]
    N = len(temp_list)
    for p1, p2 in zip(pixel_group[:N//2], pixel_group[-1:-N//2:-1]):
        pixels[p1[0],p1[1]], pixels[p2[0],p2[1]] = pixels[p2[0],p2[1]], pixels[p1[0],p1[1]]
print "swapped"

img.save('1scrambled.png')
print "saved"

Este es el resultado: (abs (r-128) + abs (g-128) + abs (b-128)) // 128 (abs (r-128) + abs (g-128) + abs (b-128)) // 128

Puede lograr resultados bastante diferentes cambiando la función hash f:

  • r-g-b:

    rgb

  • r+g/2.**8+b/2.**16:

    r + g / 2. ** 8 + b / 2. ** 16

  • math.sin(r+g*2**8+b*2**16):

    mate.sin (r + g * 2 ** 8 + b * 2 ** 16)

  • (r+g+b)//600:

    (r + g + b) // 600

  • 0:

    0 0

Emil
fuente
3
¡GUAUU! Este es genial !!! ¡Buen trabajo!
Todd Lehman
1
Esa es la más interesante hasta ahora. ¡Buen trabajo!
bebe
12

Mathematica (+ bonificación)

Esto contrae los canales de color y codifica la imagen como una larga lista de datos. El resultado es una versión codificada aún menos reconocible porque no tiene la misma distribución de colores que la original (ya que los datos también fueron codificados). Esto es más obvio en la segunda imagen codificada, pero si observa de cerca verá también el mismo efecto en la primera. La función es su propio inverso.

Hubo un comentario de que esto puede no ser válido porque codifica por canal. Creo que debería ser, pero no es gran cosa. El único cambio necesario para codificar píxeles enteros (en lugar de por canal) sería cambiar Flatten @ xa Flatten[x, 1]:)

ClearAll @ f;

f @ x_ := 
  With[
    {r = SeedRandom[Times @@ Dimensions @ x], f = Flatten @ x},
    ArrayReshape[
      Permute[f, Cycles @ Partition[RandomSample @ Range @ Length @ f, 2]],
      Dimensions @ x]]

Explicación

Define una función fque toma una matriz bidimensional x. La función utiliza el producto de las dimensiones de la imagen como una semilla aleatoria, y luego aplana la matriz a una lista unidimensional f(sombreada localmente). Luego crea una lista de la forma {1, 2, ... n}donde nestá la longitud de f, al azar permuta esa lista, la divide en segmentos de 2 (por ejemplo, {{1, 2}, {3, 4}, ...}(soltando el último número si las dimensiones son impares), y luego permuta fintercambiando los valores en las posiciones indicadas en cada sublista que acaba de crear, y finalmente vuelve a dar forma a la lista permutada a las dimensiones originales de x. Se codifica por canal porque además de contraer las dimensiones de la imagen,FlattenEl comando también contrae los datos del canal en cada píxel. La función es su propio inverso porque los ciclos incluyen solo dos píxeles cada uno.

Uso

img1=Import@"http://i.stack.imgur.com/2C2TY.jpg"//ImageData;
img2=Import@"http://i.stack.imgur.com/B5TbK.png"//ImageData;

f @ img1 // Image

ingrese la descripción de la imagen aquí

f @ f @ img1 // Image

ingrese la descripción de la imagen aquí

f @ img2 // Image

ingrese la descripción de la imagen aquí

f @ f @ img2 // Image

ingrese la descripción de la imagen aquí

Aquí está usando Flatten[x, 1].

g@x_ := With[{r = SeedRandom[Times @@ Dimensions @ x], f = Flatten[x, 1]}, 
  ArrayReshape[
   Permute[f, Cycles@Partition[RandomSample@Range@Length@f, 2]], 
   Dimensions@x]]

g@img2 // Image

ingrese la descripción de la imagen aquí

mfvonh
fuente
1
Supongo que esto no cumple con los criterios, ya que se intercambia en una escala más pequeña que la de píxeles.
trichoplax
1
No creo que sea una respuesta válida, pero también me gusta mucho. Es un giro fascinante, así que +1 de todos modos ...
trichoplax
1
@githubphagocyte Ver actualización :)
mfvonh
Gran - alcancé para el 1 de nuevo, pero por supuesto no puedo hacerlo dos veces ...
Trichoplax
1
@ githubphagocyte Oh, claro, olvido que la sintaxis corta puede ser extraña. Si. f @ f @ img1 // Imagees (en sintaxis completa)Image[f[f[img1]]]
mfvonh
10

Matlab (+ bonificación)

Básicamente, cambio la posición de dos píxeles al azar y etiqueto cada píxel que se ha cambiado para que no se vuelva a cambiar. El mismo script se puede usar nuevamente para el 'descifrado' porque reinicio el generador de números aleatorios cada vez. Esto se hace hasta que casi todos los píxeles se cambian (es por eso que el tamaño de pasos es mayor que 2)

EDITAR: Acabo de ver que Martin Büttner usó un enfoque similar, no tenía la intención de copiar la idea, comencé a escribir mi código cuando no había respuestas, lo siento mucho. Todavía creo que mi versión usa algunas ideas diferentes =) (Y mi algoritmo es mucho más ineficiente si nos fijamos en el bit donde se seleccionan las dos coordenadas aleatorias ^^)

Imágenes

1 2

Código

img = imread('shuffle_image2.bmp');
s = size(img)
rand('seed',0)
map = zeros(s(1),s(2));
for i = 1:2.1:s(1)*s(2) %can use much time if stepsize is 2 since then every pixel has to be exchanged
    while true %find two unswitched pixels
        a = floor(rand(1,2) .* [s(1),s(2)] + [1,1]);
        b = floor(rand(1,2) .* [s(1),s(2)] + [1,1]);
        if map(a(1),a(2)) == 0 && map(b(1),b(2)) == 0
            break
        end
    end
    %switch
    map(a(1),a(2)) = 1;
    map(b(1),b(2)) = 1;
    t = img(a(1),a(2),:);
    img(a(1),a(2),:) = img(b(1),b(2),:);
    img(b(1),b(2),:) = t;
end
image(img)
imwrite(img,'output2.png')
falla
fuente
No entiendo completamente, ¿su código aplicado por segunda vez en una imagen cifrada lo descifrará?
Somnium
2
Exactamente: cada vez se intercambian exactamente dos píxeles, y no se intercambiarán nuevamente durante todo el proceso. Debido a que los números 'aleatorios' son dos veces exactamente iguales (debido al restablecimiento del generador de números aleatorios), los pares de píxeles se intercambiarán de nuevo. (El RNG se basa siempre en el número generado anteriormente para generar el siguiente, espero que esto quede claro.)
error
1
Ja, esa fue mi idea inicial, pero no me molesté en asegurarme de que cada píxel se intercambiara exactamente una vez, porque tenía que ponerme a trabajar. : D +1!
Martin Ender
3
@ user2992539 Mira Octave, que es una buena alternativa de código abierto para matlab, y puedes ejecutar el 99% del código de matlab directamente en octava.
flawr
2
Creo que si entrecierras los ojos con fuerza en tus imágenes, aún puedes ver algo de estructura desde la entrada (que se debe a que no se mueven todos los píxeles). Supongo que si cambias tu algoritmo de selección para que se ejecute en O (1) en lugar de O (∞), podrías solucionarlo. ;)
Martin Ender
10

Mathematica-Use una permutación para codificar y su inverso para descifrar.

Una imagen jpg es una matriz tridimensional de {r,g,b}colores de píxeles. (Las 3 dimensiones estructuran el conjunto de píxeles por fila, columna y color). Se puede aplanar en una lista de {r,g,b}triples, luego se puede permutar de acuerdo con una lista de ciclos "conocida" y finalmente se puede volver a ensamblar en una matriz de las dimensiones originales. El resultado es una imagen revuelta.

Descifrar toma la imagen codificada y la procesa con el reverso de la lista de ciclos. Produce, sí, la imagen original.

Entonces, una sola función (en el presente caso scramble) sirve para codificar y descifrar píxeles en una imagen.

Se ingresa una imagen junto con un número inicial (para garantizar que el generador de números aleatorios estará en el mismo estado para codificar y descifrar). Cuando el parámetro, reverse, es False, la función se codificará. Cuando es verdadero, la función se descifrará.


lucha

Los píxeles se aplanan y se genera una lista aleatoria de ciclos. Permute usa ciclos para cambiar las posiciones de los píxeles en la lista aplanada.

descifrar

La misma función, scramblese utiliza para descifrar. Sin embargo, el orden de la lista de ciclos se invierte.

scramble[img_,s_,reverse_:False,imgSize_:300]:=
  Module[{i=ImageData[img],input,r},input=Flatten[i,1];SeedRandom[s];
  r=RandomSample@Range[Length[input]];Image[ArrayReshape[Permute[input,
  Cycles[{Evaluate@If[reverse,Reverse@r,r]}]],Dimensions[i]],ImageSize->imgSize]]

Ejemplos

La misma semilla (37) se usa para codificar y descifrar.

Esto produce la imagen revuelta de la montaña. La siguiente imagen muestra que la variable scrambledMount puede sustituirse por la imagen real de la escena de la montaña.

scrambledMount=scramble[mountain, 37, True]

monte1


Ahora corremos el inverso; scrambledMount se ingresa y se recupera la imagen original.

 scramble[scrambledMount, 37, True]

mount2


Lo mismo para los círculos:

círculos1


 scramble[scrambledCircles, 37, True]

círculos2

DavidC
fuente
No puedo ver cómo una imagen podría ser una matriz tridimensional.
edc65
1
@ edc65, Filas x Columnas x Colores. La imagen de la montaña es de 422 filas por 800 columnas por 3 colores. Si la matriz se aplana, produce 1012800 piezas de datos como una matriz unidimensional, es decir, como una lista.
DavidC
@ edc65 Debo agregar que Permute se usó para reorganizar los colores como rgb triplica. Ya no aplané eso porque no estaba interesado en hacer ningún cambio en la lista de colores de ningún píxel. Si considera la información rgb, {r, g, b} como un elemento, entonces estamos hablando de una matriz 2D. Visto de esta manera, tiene mucho sentido plantear la pregunta (¿Cómo podría una imagen ser una matriz tridimensional?) Que planteó. De hecho, puede ser más normal considerar una imagen como una matriz 2D, sin tener en cuenta el hecho de que los elementos rgb agregan otra dimensión.
DavidC
10

Pitón

Me gusta este rompecabezas, parecía interesante y llegué con una función de envoltura y movimiento para aplicar en la imagen.

Envuelto

Leo la imagen como un texto (de izquierda a derecha, arriba y abajo) y la escribo como una concha de caracol.

Esta función es cíclica: hay un en N, f ^ (n) (x) = x, por ejemplo, para una imagen de 4 * 2, f (f (f (x))) = x

Movimiento

Tomo un número aleatorio y muevo cada columna y línea de él

Código

# Opening and creating pictures
img = Image.open("/home/faquarl/Bureau/unnamed.png")
PM1 = img.load()
(w,h) = img.size
img2 = Image.new( 'RGBA', (w,h), "black") 
PM2 = img2.load()
img3 = Image.new( 'RGBA', (w,h), "black") 
PM3 = img3.load()

# Rotation
k = 0
_i=w-1
_j=h-1
_currentColMin = 0
_currentColMax = w-1
_currentLigMin = 0
_currentLigMax = h-1
_etat = 0
for i in range(w):
    for j in range(h):
        PM2[_i,_j]=PM1[i,j]
        if _etat==0:
            if _currentColMax == _currentColMin:
                _j -= 1
                _etat = 2
            else:
                _etat = 1
                _i -= 1
        elif _etat==1:
            _i -= 1
            if _j == _currentLigMax and _i == _currentColMin:
                _etat = 2
        elif _etat==2:
            _j -= 1
            _currentLigMax -= 1
            if _j == _currentLigMin and _i == _currentColMin:
                _etat = 5
            else:
                _etat = 3
        elif _etat==3:
            _j -= 1
            if _j == _currentLigMin and _i == _currentColMin:
                _etat = 4
        elif _etat==4:
            _i += 1
            _currentColMin += 1
            if _j == _currentLigMin and _i == _currentColMax:
                _etat = 7
            else:
                _etat = 5
        elif _etat==5:
            _i += 1
            if _j == _currentLigMin and _i == _currentColMax:
                _etat = 6
        elif _etat==6:
            _j += 1
            _currentLigMin += 1
            if _j == _currentLigMax and _i == _currentColMax:
                _etat = 1
            else:
                _etat = 7
        elif _etat==7:
            _j += 1
            if _j == _currentLigMax and _i == _currentColMax:
                _etat = 8
        elif _etat==8:
            _i -= 1
            _currentColMax -= 1
            if _j == _currentLigMax and _i == _currentColMin:
                _etat = 3
            else:
                _etat = 1
        k += 1
        if k == w * h:
            i = w
            j = h
# Movement
if w>h:z=w
else:z=h
rand.seed(z)
a=rand.randint(0,h)
for i in range(w):
  for j in range(h):
  if i%2==0:
    PM3[(i+a)%w,(j+a)%h]=PM2[i,j]
  else:
    PM3[(i-a)%w,(j-a)%h]=PM2[i,j]
# Rotate Again

Imágenes

Primera rotación: ingrese la descripción de la imagen aquí

entonces permutación: ingrese la descripción de la imagen aquí

Y con la última rotación: ingrese la descripción de la imagen aquí

En cuanto al otro ejemplo: ingrese la descripción de la imagen aquí

Faquarl
fuente
2
¿Cómo se restaura la imagen original?
trichoplax
Si es solo rotación, puedo hacerlo una cierta cantidad de tiempo (depende del tamaño). Sin embargo, si tuviera las permutaciones, no estoy seguro de si es cíclico, así que solo tengo una segunda función que solo cambia es lo que PM2 [_i, _j] = PM1 [i, j] se convirtió en PM2 [i, j] = PM1 [ _i, _j] y PM3 [(i + a)% w, (j + a)% h] = PM2 [i, j] se convirtió en PM3 [(ia)% w, (ja)% h] = PM2 [i, j]. Estoy buscando una manera de hacerlo sin cambiar estas dos líneas
Faquarl
8

VB.NET (+ bonificación)

Esto utiliza la idea de flawr, gracias a él, sin embargo, utiliza diferentes algoritmos de intercambio y verificación. El programa codifica y decodifica de la misma manera.

Imports System

Module Module1

    Sub swap(ByVal b As Drawing.Bitmap, ByVal i As Integer, ByVal j As Integer)
        Dim c1 As Drawing.Color = b.GetPixel(i Mod b.Width, i \ b.Width)
        Dim c2 As Drawing.Color = b.GetPixel(j Mod b.Width, j \ b.Width)
        b.SetPixel(i Mod b.Width, i \ b.Width, c2)
        b.SetPixel(j Mod b.Width, j \ b.Width, c1)
    End Sub

    Sub Main(ByVal args() As String)
        For Each a In args
            Dim f As New IO.FileStream(a, IO.FileMode.Open)
            Dim b As New Drawing.Bitmap(f)
            f.Close()
            Dim sz As Integer = b.Width * b.Height - 1
            Dim w(sz) As Boolean
            Dim r As New Random(666)
            Dim u As Integer, j As Integer = 0
            Do While j < sz
                Do
                    u = r.Next(0, sz)
                Loop While w(u)
                ' swap
                swap(b, j, u)
                w(j) = True
                w(u) = True
                Do
                    j += 1
                Loop While j < sz AndAlso w(j)
            Loop
            b.Save(IO.Path.ChangeExtension(a, "png"), Drawing.Imaging.ImageFormat.Png)
            Console.WriteLine("Done!")
        Next
    End Sub

End Module

Imágenes de salida:

Somnium
fuente
8

Después de recordar que esto está a punto de intercambiar píxeles y no alterarlos, aquí está mi solución para esto:

Revuelto: ingrese la descripción de la imagen aquí

Restaurado: ingrese la descripción de la imagen aquí

Esto se hace aleatorizando el orden de píxeles, pero para poder restaurarlo, la aleatorización es fija. Esto se hace utilizando un pseudoaleatorio con una semilla fija y genera una lista de índices que describen qué píxeles intercambiar. Como el intercambio será el mismo, la misma lista restaurará la imagen original.

public class ImageScramble {

  public static void main(String[] args) throws IOException {
    if (args.length < 2) {
      System.err.println("Usage: ImageScramble <fileInput> <fileOutput>");
    } else {
      // load image
      final String extension = args[0].substring(args[0].lastIndexOf('.') + 1);
      final BufferedImage image = ImageIO.read(new File(args[0]));
      final int[] pixels = image.getRGB(0, 0, image.getWidth(), image.getHeight(), null, 0, image.getWidth());

      // create randomized swap list
      final ArrayList<Integer> indexes = IntStream.iterate(0, i -> i + 1).limit(pixels.length).collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
      Collections.shuffle(indexes, new Random(1337));

      // swap all pixels at index n with pixel at index n+1
      int tmp;
      for (int i = 0; i < indexes.size(); i += 2) {
        tmp = pixels[indexes.get(i)];
        pixels[indexes.get(i)] = pixels[indexes.get(i + 1)];
        pixels[indexes.get(i + 1)] = tmp;
      }

      // write image to disk
      final BufferedImage imageScrambled = new BufferedImage(image.getWidth(), image.getHeight(), image.getType());
      imageScrambled.setRGB(0, 0, imageScrambled.getWidth(), imageScrambled.getHeight(), pixels, 0, imageScrambled.getWidth());
      ImageIO.write(imageScrambled, extension, new File(args[1]));
    }
  }
}

Tenga en cuenta que el uso de este algoritmo en un formato de compresión con pérdida no producirá el mismo resultado, ya que el formato de imagen alterará los datos. Esto debería funcionar bien con cualquier códec sin pérdidas como PNG.

TwoThe
fuente
8

Mathematica

Definimos una función auxiliar hy la función de codificación scramblecomo:

h[l_List, n_Integer, k_Integer: 1] := 
  With[{ m = Partition[l, n, n, 1, 0] }, 
    Flatten[
      Riffle[
        RotateLeft[ m[[ ;; , {1} ]] , k ],
        m[[ ;; , 2;; ]]
      ], 1
    ] [[ ;; Length[l] ]]
  ];

scramble[img_Image, k_Integer] :=
  Module[{ list , cNum = 5 },
    Which[
      k > 0,    list = Prime@Range[cNum],
      k < 0,    list = Reverse@Prime@Range[cNum],
      True,     list = {}
    ];
    Image[
      Transpose[
        Fold[ h[ #1, #2, k ] &, #, list ] & /@
        Transpose[
          Fold[ h[#1, #2, k] &, #, list ] & /@ ImageData[img]
        ]
      ]
    ]
  ];

Después de cargar una imagen, puede llamar a scramble[img, k]donde kestá cualquier número entero para codificar la imagen. Llamar de nuevo con -kse descifrará. (Si kes así 0, entonces no se realiza ningún cambio). Por klo general, debe elegirse para ser algo así 100, lo que da una imagen bastante codificada:

Ejemplo de salida 1

Ejemplo de salida 2

Frxstrem
fuente
7

Matlab: codificación de filas y columnas basada en variaciones de suma de filas / columnas

Esto parecía un rompecabezas divertido, así que lo pensé y se me ocurrió la siguiente función. Se basa en la invariancia de las sumas de valor de píxel de fila y columna durante el desplazamiento circular: desplaza cada fila, luego cada columna, por la suma total de los valores de píxel de la fila / columna (suponiendo un uint8 para el número entero en la variable de desplazamiento ) Esto se puede revertir desplazando cada columna y luego remar por su valor de suma en la dirección opuesta.

No es tan bonito como los demás, pero me gusta que no sea aleatorio y esté completamente especificado por la imagen, sin elegir parámetros.

Originalmente lo diseñé para cambiar cada canal de color por separado, pero luego noté la especificación para mover solo píxeles completos.

function pic_scramble(input_filename)
i1=imread(input_filename);
figure;
subplot(1,3,1);imagesc(i1);title('Original','fontsize',20);

i2=i1;
for v=1:size(i1,1)
    i2(v,:,:)=circshift(i2(v,:,:),sum(sum(i2(v,:,:))),2);
end
for w=1:size(i2,2)
    i2(:,w,:)=circshift(i2(:,w,:),sum(sum(i2(:,w,:))),1);
end
subplot(1,3,2);imagesc(i2);title('Scrambled','fontsize',20);

i3=i2;
for w=1:size(i3,2)
    i3(:,w,:)=circshift(i3(:,w,:),-1*sum(sum(i3(:,w,:))),1);
end
for v=1:size(i3,1)
    i3(v,:,:)=circshift(i3(v,:,:),-1*sum(sum(i3(v,:,:))),2);
end
subplot(1,3,3);imagesc(i3);title('Recovered','fontsize',20);

Primera imagen de prueba Imagen de prueba secundaria

Hugh Nolan
fuente
6

Java

Este programa intercambia píxeles al azar (crea mapeo de píxel a píxel), pero en lugar de una función aleatoria, usa Math.sin () (entero x). Es totalmente reversible. Con imágenes de prueba crea algunos patrones.

Parámetros: número entero (número de pasadas, número negativo para revertir, 0 no hace nada), imagen de entrada e imagen de salida (puede ser lo mismo). El archivo de salida debe estar en formato que use compresión sin pérdidas.

1 pase: ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

100 pases (se tarda unos minutos en hacerlo): ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

Código:

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

public class Test{

public static void main(String... args) {
    String in = "image.png";
    String out = in;
    int passes = 0;
    if (args.length < 1) {
        System.out.println("no paramem encryptimg, 1 pass, reading and saving image.png");
        System.out.println("Usage: pass a number. Negative - n passes of decryption, positive - n passes of encryption, 0 - do nothing");
    } else {
        passes = Integer.parseInt(args[0]);
        if (args.length > 1) {
            in = args[1];
        }
        if(args.length > 2){
            out = args[2];
        }
    }
    boolean encrypt = passes > 0;
    passes = Math.abs(passes);
    for (int a = 0; a < passes; a++) {
        BufferedImage img = null;
        try {
            img = ImageIO.read(new File(a == 0 ? in : out));
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }
        int pixels[][] = new int[img.getWidth()][img.getHeight()];
        int[][] newPixels = new int[img.getWidth()][img.getHeight()];
        for (int x = 0; x < pixels.length; x++) {
            for (int y = 0; y < pixels[x].length; y++) {
                pixels[x][y] = img.getRGB(x, y);
            }
        }
        int amount = img.getWidth() * img.getHeight();
        int[] list = new int[amount];
        for (int i = 0; i < amount; i++) {
            list[i] = i;
        }
        int[] mapping = new int[amount];
        for (int i = amount - 1; i >= 0; i--) {
            int num = (Math.abs((int) (Math.sin(i) * amount))) % (i + 1);
            mapping[i] = list[num];
            list[num] = list[i];
        }
        for (int xz = 0; xz < amount; xz++) {
            int x = xz % img.getWidth();
            int z = xz / img.getWidth();
            int xzMap = mapping[xz];
            int newX = xzMap % img.getWidth();
            int newZ = xzMap / img.getWidth();
            if (encrypt) {
                newPixels[x][z] = pixels[newX][newZ];
            } else {
                newPixels[newX][newZ] = pixels[x][z];
            }
        }
        BufferedImage newImg = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);
        for (int x = 0; x < pixels.length; x++) {
            for (int y = 0; y < pixels[x].length; y++) {
                newImg.setRGB(x, y, newPixels[x][y]);
            }
        }

        try {
            String[] s = out.split("\\.");
            ImageIO.write(newImg, s[s.length - 1],
                    new File(out));
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }
    }
}
}
barteks2x
fuente
6

Python 2.7 con PIL

Un poco tarde para la fiesta, pero pensé que sería divertido convertir las imágenes en cuadros (y al revés, por supuesto). Primero, desplazamos las columnas hacia arriba o hacia abajo 4 veces el número de columnas (columnas pares hacia abajo, columnas impares hacia arriba). Luego, desplazamos las filas hacia la izquierda o hacia la derecha 4 veces el número de la fila (columnas pares a la izquierda, columnas impares a la derecha).

El resultado es bastante tartán.

Para revertir, solo hacemos esto en el orden opuesto y cambiamos por la cantidad opuesta.

Código

from PIL import Image

def slideColumn (pix, tpix, x, offset, height):
  for y in range(height):
    tpix[x,(offset+y)%height] = pix[x,y]

def slideRow (pix, tpix, y, offset, width):
  for x in range(width):
    tpix[(offset+x)%width,y] = pix[x,y]

def copyPixels (source, destination, width, height):
  for x in range(width):
    for y in range(height):
      destination[x,y]=source[x,y]

def shuffleHorizontal (img, tmpimg, factor, encoding):
  xsize,ysize = img.size
  pix = img.load()
  tpix = tmpimg.load()
  for y in range(ysize):
    offset = y*factor
    if y%2==0:
      offset = xsize-offset
    offset = (xsize + offset) % xsize
    if encoding:
      slideRow(pix,tpix,y,offset,xsize)
    else:
      slideRow(pix,tpix,y,-offset,xsize)
  copyPixels(tpix,pix,xsize,ysize)

def shuffleVertical (img, tmpimg, factor, encoding):
  xsize,ysize = img.size
  pix = img.load()
  tpix = tmpimg.load()
  for x in range(xsize):
    offset = x*factor
    if x%2==0:
      offset = ysize-offset
    offset = (ysize + offset) % ysize
    if encoding:
      slideColumn(pix,tpix,x,offset,ysize)
    else:
      slideColumn(pix,tpix,x,-offset,ysize)
  copyPixels(tpix,pix,xsize,ysize)


def plaidify (img):
  tmpimg = Image.new("RGB",img.size)
  shuffleVertical(img,tmpimg,4,True)
  shuffleHorizontal(img,tmpimg,4,True)

def deplaidify (img):
  tmpimg = Image.new("RGB",img.size)
  shuffleHorizontal(img,tmpimg,4,False)
  shuffleVertical(img,tmpimg,4,False)

Resultados

La tela escocesa de la imagen 1:

el 1.jpg plaidificado

La imagen en forma de cuadros 2:

el plaidificado 2.png

jrrl
fuente
2
¡Muy agradable! ¿Es posible obtener las diagonales a lo largo de un ángulo de 45 °?
Todd Lehman
2
Es posible cambiando las líneas de desplazamiento a: offset = x*xsize/ysize y offset = y*ysize/xsize , desafortunadamente, realmente no oculta la imagen también.
jrrl
5

Python (+ bonus) - permutación de los píxeles

En este método, cada píxel se colocará en otra posición, con la restricción de que el otro píxel se colocará en la primera posición. Matemáticamente, es una permutación con la longitud del ciclo 2. Como tal, el método es su propio inverso.

En retrospectiva, es muy similar a mfvonh, pero esta presentación está en Python y tuve que construir esa permutación yo mismo.

def scramble(I):
    result = np.zeros_like(I)
    size = I.shape[0:2]
    nb_pixels = size[0]*size[1]
    #Build permutation
    np.random.seed(0)
    random_indices = np.random.permutation( range(nb_pixels) )
    random_indices1 = random_indices[0:int(nb_pixels/2)]
    random_indices2 = random_indices[-1:-1-int(nb_pixels/2):-1]
    for c in range(3):
        Ic = I[:,:,c].flatten()
        Ic[ random_indices2 ] = Ic[random_indices1]
        Ic[ random_indices1 ] = I[:,:,c].flatten()[random_indices2]
        result[:,:,c] = Ic.reshape(size)
    return result

Primera imagen de prueba: Primera imagen de prueba Segunda imagen de prueba: Segunda imagen de prueba

Dave
fuente
5

Python 2.7 + PIL, inspiración de los rompecabezas deslizantes

Acabo de tener otra idea. Básicamente, este método divide una imagen en bloques de igual tamaño y luego baraja su orden. Dado que el nuevo orden se basa en una semilla fija, es posible revertir completamente el proceso usando la misma semilla. Además, con el parámetro adicional llamado granularidad, es posible lograr resultados diferentes e irreconocibles.

Resultados:

Original

original

Granularidad 16

dieciséis

Granularidad 13

13

Granularidad 10

10

Granularidad 3

3

Granularidad 2

2

Granularidad 1

ingrese la descripción de la imagen aquí

Original

original

Granularidad 16

dieciséis

Granularidad 13

13

Granularidad 10

10

Granularidad 3

3

Granularidad 2

2

Granularidad 1

1

Código:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from PIL import Image
import random

def scramble_blocks(im,granularity,password,nshuffle):
    set_seed(password)
    width=im.size[0]
    height=im.size[1]

    block_width=find_block_dim(granularity,width)       #find the possible block dimensions
    block_height=find_block_dim(granularity,height)

    grid_width_dim=width/block_width                #dimension of the grid
    grid_height_dim=height/block_height

    nblocks=grid_width_dim*grid_height_dim          #number of blocks

    print "nblocks: ",nblocks," block width: ",block_width," block height: ",block_height
    print "image width: ",width," image height: ",height
    print "getting all the blocks ..."
    blocks=[]
    for n in xrange(nblocks): #get all the image blocks
        blocks+=[get_block(im,n,block_width,block_height)]

    print "shuffling ..."
    #shuffle the order of the blocks
    new_order=range(nblocks)
    for n in xrange(nshuffle):
        random.shuffle(new_order)

    print "building final image ..."
    new_image=im.copy()
    for n in xrange(nblocks):
        #define the target box where to paste the new block
        i=(n%grid_width_dim)*block_width                #i,j -> upper left point of the target image
        j=(n/grid_width_dim)*block_height
        box = (i,j,i+block_width,j+block_height)    

        #paste it   
        new_image.paste(blocks[new_order[n]],box)

    return new_image



#find the dimension(height or width) according to the desired granularity (a lower granularity small blocks)
def find_block_dim(granularity,dim):
    assert(granularity>0)
    candidate=0
    block_dim=1
    counter=0
    while counter!=granularity:         #while we dont achive the desired granularity
        candidate+=1
        while((dim%candidate)!=0):      
            candidate+=1
            if candidate>dim:
                counter=granularity-1
                break

        if candidate<=dim:
            block_dim=candidate         #save the current feasible lenght

        counter+=1

    assert(dim%block_dim==0 and block_dim<=dim)
    return block_dim

def unscramble_blocks(im,granularity,password,nshuffle):
    set_seed(password)
    width=im.size[0]
    height=im.size[1]

    block_width=find_block_dim(granularity,width)       #find the possible block dimensions
    block_height=find_block_dim(granularity,height)

    grid_width_dim=width/block_width                #dimension of the grid
    grid_height_dim=height/block_height

    nblocks=grid_width_dim*grid_height_dim          #number of blocks

    print "nblocks: ",nblocks," block width: ",block_width," block height: ",block_height
    print "getting all the blocks ..."
    blocks=[]
    for n in xrange(nblocks): #get all the image blocks
        blocks+=[get_block(im,n,block_width,block_height)]

    print "shuffling ..."
    #shuffle the order of the blocks
    new_order=range(nblocks)
    for n in xrange(nshuffle):
        random.shuffle(new_order)

    print "building final image ..."
    new_image=im.copy()
    for n in xrange(nblocks):
        #define the target box where to paste the new block
        i=(new_order[n]%grid_width_dim)*block_width             #i,j -> upper left point of the target image
        j=(new_order[n]/grid_width_dim)*block_height
        box = (i,j,i+block_width,j+block_height)    

        #paste it   
        new_image.paste(blocks[n],box)

    return new_image

#get a block of the image
def get_block(im,n,block_width,block_height):

    width=im.size[0]

    grid_width_dim=width/block_width                        #dimension of the grid

    i=(n%grid_width_dim)*block_width                        #i,j -> upper left point of the target block
    j=(n/grid_width_dim)*block_height

    box = (i,j,i+block_width,j+block_height)
    block_im = im.crop(box)
    return block_im

#set random seed based on the given password
def set_seed(password):
    passValue=0
    for ch in password:                 
        passValue=passValue+ord(ch)
    random.seed(passValue)


if __name__ == '__main__':

    filename="0RT8s.jpg"
    # filename="B5TbK.png"
    password="yOs0ZaKpiS"
    nshuffle=1
    granularity=1

    im=Image.open(filename)

    new_image=scramble_blocks(im,granularity,password,nshuffle)
    new_image.show()
    new_image.save(filename.split(".")[0]+"_puzzled.png")

    new_image=unscramble_blocks(new_image,granularity,password,nshuffle)
    new_image.save(filename.split(".")[0]+"_unpuzzled.png")
    new_image.show()
AlexPnt
fuente
5

47

94 líneas. 47 para codificar, 47 para decodificar.

require 'chunky_png'
require_relative 'codegolf-35005_ref.rb'


REF = {:png => ref, :w => 1280, :h => 720}
REF[:pix] = REF[:png].to_rgb_stream.unpack('C*').each_slice(3).to_a
SEVENTH_PRIME = 4*7 - 4-7 - (4&7)
FORTY_SEVEN   = 4*7 + 4+7 + (4&7) + (4^7) + 7/4
THRESHOLD     = FORTY_SEVEN * SEVENTH_PRIME


class RNG
    @@m = 2**32
    @@r = 0.5*(Math.sqrt(5.0) - 1.0)
    def initialize(n=0)
        @cur = FORTY_SEVEN + n
    end
    def hash(seed)
        (@@m*((seed*@@r)%1)).floor
    end
    def _next(max)
        hash(@cur+=1) % max
    end
    def _prev(max)
        hash(@cur-=1) % max
    end        
    def advance(n)
        @cur += n
    end
    def state
        @cur
    end
    alias_method :rand, :_next
end


def load_png(file, resample_w = nil, resample_h = nil)
    png  = ChunkyPNG::Image.from_file(file)
    w    = resample_w || png.width
    h    = resample_h || png.height
    png.resample_nearest_neighbor!(w,h) if resample_w || resample_h
    pix  = png.to_rgb_stream.unpack('C*').each_slice(3).to_a
    return {:png => png, :w => w, :h => h, :pix => pix}
end


def make_png(img)
    rgb_stream = img[:pix].flatten.pack('C*')
    img[:png] = ChunkyPNG::Canvas.from_rgb_stream(img[:w],img[:h],rgb_stream)
    return img
end


def difference(pix_a,pix_b)
    (pix_a[0]+pix_a[1]+pix_a[2]-pix_b[0]-pix_b[1]-pix_b[2]).abs
end


def code(img, img_ref, mode)
    img_in  = load_png(img)
    pix_in  = img_in[:pix]
    pix_ref = img_ref[:pix]
    s = img_in[:w] * img_in[:h] 
    rng = RNG.new(mode==:enc ? 0 : FORTY_SEVEN*s+1)
    rand = mode == :enc ? rng.method(:_next) : rng.method(:_prev)
    s.times do
        FORTY_SEVEN.times do
            j = rand.call(s)
            i = rng.state % s
            diff_val = difference(pix_ref[i],pix_ref[j])
            if diff_val > THRESHOLD
               pix_in[i], pix_in[j] = pix_in[j], pix_in[i]
            end
        end
    end
    make_png(img_in)
end


case ARGV.shift
when 'enc'
    org, cod = ARGV
    encoded_image = code(org,REF,:enc)
    encoded_image[:png].save(cod)
when 'dec'
    org, cod = ARGV
    decoded_image = code(cod,REF,:dec)
    decoded_image[:png].save(org)
else
    puts '<original> <coded>'
    puts 'specify either <enc> or <dec>'
    puts "ruby #{$0} enc codegolf-35005_inp.png codegolf-35005_enc.png"
end

codegolf-35005_ref.rb

ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

(convertido a jpg)

ingrese la descripción de la imagen aquí

(original reducido)

ingrese la descripción de la imagen aquí

blutorange
fuente
3
Parte del patrón original es visible a través de esas líneas. Sin embargo, se parece cuando dibujas con un dedo en la ventana empañada).
Somnium
2
... + 184426 bytes para codegolf-35005_ref.rb?
edc65
5

Matlab con una pizca de teoría de grupo (+ bonus)

En este enfoque, suponemos que tenemos un número par de píxeles totales. (Si no, simplemente ignoramos un píxel). Por lo tanto, debemos elegir la mitad de los píxeles para intercambiar con la otra mitad. Para esto, indexamos todos los píxeles desde 0hasta 2N-1y consideramos estos índices como un grupo cíclico.

Entre los números primos buscamos un número pque no sea demasiado pequeño ni demasiado grande, y que sea coprimo para 2Nel orden de nuestro grupo. Esto significa que g genera nuestro grupo o {k*g mod 2N | k=0,1,...,2N-1} = {0,1,...,2N-1}.

Por lo tanto, elegimos los primeros Nmúltiplos de gcomo un conjunto, y todos los indeces restantes como el otro conjunto, y simplemente intercambiamos el conjunto de píxeles correspondiente.

Si pse elige de la manera correcta, el primer conjunto se distribuye uniformemente en toda la imagen.

Los dos casos de prueba:

Ligeramente fuera de tema pero interesante:

Durante las pruebas, noté que si lo guarda en un jpg (comprimido con pérdida) (en lugar de un png comprimido sin pérdida) y aplica la transformación de un lado a otro, verá rápidamente artefactos de la compresión, esto muestra los resultados de dos reordenamientos consecutivos :

Como puede ver, la compresión jpg hace que el resultado se vea casi en blanco y negro.

clc;clear;
inputname = 'codegolf_rearrange_pixels2.png';
inputname = 'codegolf_rearrange_pixels2_swapped.png';
outputname = 'codegolf_rearrange_pixels2_swapped.png';

%read image
src = imread(inputname);

%separate into channels
red = src(:,:,1);
green = src(:,:,2);
blue = src(:,:,3);

Ntotal = numel(red(:));  %number of pixels
Nswap = floor(Ntotal/2); %how many pairs we can swap

%find big enough generator
factors = unique(factor(Ntotal));
possible_gen = primes(max(size(red)));
eliminated = setdiff(possible_gen,factors);
if mod(numel(eliminated),2)==0 %make length odd for median
    eliminated = [1,eliminated];
end
generator = median(eliminated);

%set up the swapping vectors
swapindices1 = 1+mod((1:Nswap)*generator, Ntotal);
swapindices2 = setdiff(1:Ntotal,swapindices1);
swapindices2 = swapindices2(1:numel(swapindices1)); %make sure both have the same length

%swap the pixels
red([swapindices1,swapindices2]) = red([swapindices2,swapindices1]);
green([swapindices1,swapindices2]) = green([swapindices2,swapindices1]);
blue([swapindices1,swapindices2]) = blue([swapindices2,swapindices1]);

%write and display
output = cat(3,red,green,blue);
imwrite(output,outputname);
subplot(2,1,1);
imshow(src)
subplot(2,1,2);
imshow(output);
falla
fuente
4

JavaScript (+ bonificación) - repetidor de intercambio de división de píxeles

La función toma un elemento de imagen y

  1. Divide los píxeles entre 8.
  2. Hace un intercambio reversible de grupos de píxeles.
  3. Intercambio recurrente si el grupo de píxeles> = 8.
function E(el){
    var V=document.createElement('canvas')
    var W=V.width=el.width,H=V.height=el.height,C=V.getContext('2d')
    C.drawImage(el,0,0)
    var id=C.getImageData(0,0,W,H),D=id.data,L=D.length,i=L/4,A=[]
    for(;--i;)A[i]=i
    function S(A){
        var L=A.length,x=L>>3,y,t,i=0,s=[]
        if(L<8)return A
        for(;i<L;i+=x)s[i/x]=S(A.slice(i,i+x))
        for(i=4;--i;)y=[6,4,7,5,1,3,0,2][i],t=s[i],s[i]=s[y],s[y]=t
        s=[].concat.apply([],s)
        return s
    }
    var N=C.createImageData(W,H),d=N.data,A=S(A)
    for(var i=0;i<L;i++)d[i]=D[(A[i>>2]*4)+(i%4)]
    C.putImageData(N,0,0)
    el.src=C.canvas.toDataURL()
}

Montañas Círculos

martillo de lobo
fuente
4

Python 2.7 + PIL, Scrambler de columna / fila

Este método simplemente codifica las filas y columnas de la imagen. Es posible mezclar solo una de las dimensiones o ambas. Además, el orden de la nueva fila / columna codificada se basa en una contraseña. Además, otra posibilidad es mezclar toda la matriz de imágenes sin tener en cuenta las dimensiones.

Resultados:

Mezclando toda la imagen:

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

Aleatorizando las columnas:

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

Aleatorizando las filas:

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

Mezclando columnas y filas:

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

También intenté aplicar varias corridas a la imagen, pero los resultados finales no diferían mucho, solo la dificultad para descifrarlo.

Código:

from PIL import Image
import random,copy

def scramble(im,columns,rows):
    pixels =list(im.getdata())

    newOrder=range(columns*rows)        
    random.shuffle(newOrder)            #shuffle

    newpixels=copy.deepcopy(pixels)
    for i in xrange(len(pixels)):
        newpixels[i]=pixels[newOrder[i]]

    im.putdata(newpixels)

def unscramble(im,columns,rows):
    pixels =list(im.getdata())

    newOrder=range(columns*rows)        
    random.shuffle(newOrder)            #unshuffle

    newpixels=copy.deepcopy(pixels)
    for i in xrange(len(pixels)):
        newpixels[newOrder[i]]=pixels[i]

    im.putdata(newpixels)

def scramble_columns(im,columns,rows):
    pixels =list(im.getdata())

    newOrder=range(columns)     
    random.shuffle(newOrder)            #shuffle

    newpixels=[]
    for i in xrange(rows):
        for j in xrange(columns):
            newpixels+=[pixels[i*columns+newOrder[j]]]

    im.putdata(newpixels)

def unscramble_columns(im,columns,rows):
    pixels =list(im.getdata())

    newOrder=range(columns)     
    random.shuffle(newOrder)            #shuffle

    newpixels=copy.deepcopy(pixels)
    for i in xrange(rows):
        for j in xrange(columns):
            newpixels[i*columns+newOrder[j]]=pixels[i*columns+j]

    im.putdata(newpixels)

def scramble_rows(im,columns,rows):
    pixels =list(im.getdata())

    newOrder=range(rows)        
    random.shuffle(newOrder)            #shuffle the order of pixels

    newpixels=copy.deepcopy(pixels)
    for j in xrange(columns):
        for i in xrange(rows):
            newpixels[i*columns+j]=pixels[columns*newOrder[i]+j]

    im.putdata(newpixels)

def unscramble_rows(im,columns,rows):
    pixels =list(im.getdata())

    newOrder=range(rows)        
    random.shuffle(newOrder)            #shuffle the order of pixels

    newpixels=copy.deepcopy(pixels)
    for j in xrange(columns):
        for i in xrange(rows):
            newpixels[columns*newOrder[i]+j]=pixels[i*columns+j]

    im.putdata(newpixels)


#set random seed based on the given password
def set_seed(password):
    passValue=0
    for ch in password:                 
        passValue=passValue+ord(ch)
    random.seed(passValue)

def encrypt(im,columns,rows,password):
    set_seed(password)
    # scramble(im,columns,rows)
    scramble_columns(im,columns,rows)
    scramble_rows(im,columns,rows)

def decrypt(im,columns,rows,password):
    set_seed(password)
    # unscramble(im,columns,rows)
    unscramble_columns(im,columns,rows)
    unscramble_rows(im,columns,rows)

if __name__ == '__main__':
    passwords=["yOs0ZaKpiS","NA7N3v57og","Nwu2T802mZ","6B2ec75nwu","FP78XHYGmn"]
    iterations=1
    filename="0RT8s.jpg"
    im=Image.open(filename)
    size=im.size
    columns=size[0]
    rows=size[1]

    for i in range(iterations):
        encrypt(im,columns,rows,passwords[i])
    im.save(filename.split(".")[0]+"_encrypted.jpg")

    for i in range(iterations):
        decrypt(im,columns,rows,passwords[iterations-i-1])
    im.save(filename.split(".")[0]+"_decrypted.jpg")
AlexPnt
fuente
3

C # Winforms

Imagen1: ingrese la descripción de la imagen aquí

Imagen 2: ingrese la descripción de la imagen aquí

Código fuente:

class Program
{
    public static void codec(String src, String trg, bool enc)
    {
        Bitmap bmp = new Bitmap(src);
        Bitmap dst = new Bitmap(bmp.Width, bmp.Height);

        List<Point> points = new List<Point>();
        for (int y = 0; y < bmp.Height; y++)
            for (int x = 0; x < bmp.Width; x++)
                points.Add(new Point(x, y));

        for (int y = 0; y < bmp.Height; y++)
        {
            for (int x = 0; x < bmp.Width; x++)
            {
                int py = Convert.ToInt32(y + 45 * Math.Sin(2.0 * Math.PI * x / 128.0));
                int px = Convert.ToInt32(x + 45 * Math.Sin(2.0 * Math.PI * y / 128.0));

                px = px < 0 ? 0 : px;
                py = py < 0 ? 0 : py;
                px = px >= bmp.Width ? bmp.Width - 1 : px;
                py = py >= bmp.Height ? bmp.Height - 1 : py;

                int srcIndex = x + y * bmp.Width;
                int dstIndex = px + py * bmp.Width;

                Point temp = points[srcIndex];
                points[srcIndex] = points[dstIndex];
                points[dstIndex] = temp;
            }
        }

        for (int y = 0; y < bmp.Height; y++)
        {
            for (int x = 0; x < bmp.Width; x++)
            {
                Point p = points[x + y * bmp.Width];
                if (enc)
                    dst.SetPixel(x, y, bmp.GetPixel(p.X, p.Y));
                else
                    dst.SetPixel(p.X, p.Y, bmp.GetPixel(x, y));
            }
        }

        dst.Save(trg);
    }


    static void Main(string[] args)
    {
        // encode
        codec(@"c:\shared\test.png", @"c:\shared\test_enc.png", true);

        // decode
        codec(@"c:\shared\test_enc.png", @"c:\shared\test_dec.png", false);
    }
}
Johan du Toit
fuente
1

Python 3.6 + pypng

Riffle / Master Shuffle

#!/usr/bin/env python3.6

import argparse
import itertools

import png

def read_image(filename):
    img = png.Reader(filename)
    w, h, data, meta = img.asRGB8()
    return w, h, list(itertools.chain.from_iterable(
        [
            (row[i], row[i+1], row[i+2])
            for i in range(0, len(row), 3)
        ]
        for row in data
    ))

def riffle(img, n=2):
    l = len(img)
    base_size = l // n
    big_groups = l % n
    base_indices = [0]
    for i in range(1, n):
        base_indices.append(base_indices[-1] + base_size + int(i <= big_groups))
    result = []
    for i in range(0, base_size):
        for b in base_indices:
            result.append(img[b + i])
    for i in range(big_groups):
        result.append(img[base_indices[i] + base_size])
    return result

def master(img, n=2):
    parts = [[] for _ in range(n)]
    for i, pixel in enumerate(img):
        parts[i % n].append(pixel)
    return list(itertools.chain.from_iterable(parts))

def main():
    parser = argparse.ArgumentParser()

    parser.add_argument('infile')
    parser.add_argument('outfile')
    parser.add_argument('-r', '--reverse', action='store_true')
    parser.add_argument('-i', '--iterations', type=int, default=1)
    parser.add_argument('-n', '--groupsize', type=int, default=2)
    parser.add_argument('-c', '--complex', nargs='+', type=int)

    args = parser.parse_args()

    w, h, img = read_image(args.infile)

    if args.complex:
        if any(-1 <= n <= 1 for n in args.complex):
            parser.error("Complex keys must use group sizes of at least 2")
        if args.reverse:
            args.complex = [
                -n for n in reversed(args.complex)
            ]
        for n in args.complex:
            if n > 1:
                img = riffle(img, n)
            elif n < -1:
                img = master(img, -n)
    elif args.reverse:
        for _ in range(args.iterations):
            img = master(img, args.groupsize)
    else:
        for _ in range(args.iterations):
            img = riffle(img, args.groupsize)

    writer = png.Writer(w, h)
    with open(args.outfile, 'wb') as f:
        writer.write_array(f, list(itertools.chain.from_iterable(img)))


if __name__ == '__main__':
    main()

Mi algoritmo aplica el riffle shuffle en una dirección y un master shuffle en la otra (dado que los dos son inversos entre sí), varias iteraciones cada una, pero cada una se generaliza para dividirse en cualquier número de subgrupos en lugar de solo dos. El efecto es que podría crear una clave de permutación de iteración múltiple, ya que la imagen no se restaurará sin conocer la secuencia exacta de riffle y shuffle maestro. Se puede especificar una secuencia con una serie de enteros, con números positivos que representan rifles y números negativos que representan maestros.

Barajé el paisaje con la tecla [3, -5, 2, 13, -7]:

Paisaje 3 -5 2 13 -7

Curiosamente, algunas cosas interesantes suceden desde [3, -5], donde quedan algunos artefactos de la imagen original:

Paisaje 3 -5

Aquí está el patrón abstracto barajado con la tecla [2, 3, 5, 7, -11, 13, -17]:

Círculos 2 3 5 7-11 13-17

Si solo hay un parámetro incorrecto en la clave, la desorganización no restaurará la imagen:

Bad Unshuffle

Carne de res
fuente