Showing posts with label image destruction. Show all posts
Showing posts with label image destruction. Show all posts

Degrading jpeg images with repeated rotation - via Bash (FFmpeg and ImageMagick)



A continuation of the decade-old topic of degrading jpeg images by repeated rotation and saving. This post briefly demonstrates the process using FFmpeg and ImageMagick in a Bash script. Previously, a Python script achieving similar results was published, which has recently been updated. There are many posts on this subject and they can all be accessed by searching for 'jpeg rotation' tag.
posts: https://oioiiooixiii.blogspot.com/search/label/jpeg%20rotation
The two basic commands are below. Both versions rotate an image 90 degrees clockwise, and each overwrite the original image. They should be run inside a loop to create progressively more degraded images.

ImageMagick: The quicker of the two, it uses the standard 'libjpg' library for saving images.
mogrify -rotate "90" -quality "74" "image.jpg"

FFmpeg: Saving is done with the 'mjpeg' encoder, creating significantly different results.
ffmpeg -i "image.jpg" -vf "transpose=1" -q:v 12 "image.jpg" -y

There are many options and ways to extend each of the basic commands. For FFmpeg, one such way is to use the 'noise' filter to help create entropy in the image while running. It also has the effect of discouraging the gradual magenta-shift caused by the mjpeg encoder.

A functional (but basic) Bash script is presented later in this blog post. It allows for the choice between ImageMagick or FFmpeg versions, as well as allowing some other parameters to be set. Directly below is another montage of images created using the script. Run-time parameters for each result are given at the end of this post.



Running the script without any arguments (except for the image file name) will invoke ImageMagick's 'mogrify' command, rotating the image 500 times, and saving at a jpeg quality of '74'. Note that when the FFmpeg version is running, the jpeg quality is crudely inverted, to use the 'q:v' value of the 'mjpeg' encoder.

The parameters for the script: [filename: string] [rotations: 1-n] [quality: 1-100] [frames: (any string)] [version: (any string for FFmpeg)] [noise: 1-100]
#!/bin/bash
# Simple Bash script to degrade a jpeg image by repeated rotations and saves,
# using either FFmpeg or ImageMagick. N.B. Starting image must be a jpeg.

# Example: rotateDegrade.sh "image.jpg" "1200" "67" "no" "FFmpeg" "21"
# Run on image.jpg, 1200 rotations, quality=67, no frames, use FFmpeg, noise=21

# source: oioiiooixiii.blogspot.com
# version: 2019.08.22_13.57.37

# All relevent code resides in this function
function rotateDegrade()
{
   local rotations="${2:-500}" # number of rotations
   local quality="${3:-74}" # Jpeg save quality (note inverse value for FFmpeg)
   local saveInterim="${4:-no}" # To save every full rotation as a new frame
   local version="${5:-IM}" # Choice of function (any other string for FFmpeg)
   local ffNoise="${6:-0}" # FFmpeg noise filter

   # Name of new file created to work on
   local workingFile="${1}_r${rotations}-q${quality}-${version}-n${ffNoise}.jpg"
   cp "$1" "$workingFile" # make a copy of the input file to work on
   # N.B. consider moving above file to volatile memory e.g. /dev/shm

   # ImageMagick and FFmpeg sub-functions
   function rotateImageMagick() {
      mogrify -rotate "90" -quality "$quality" "$workingFile"; }
   function rotateFFmpeg() {
      ffmpeg -i "$workingFile" -vf "format=rgb24,transpose=1,
         noise=alls=${ffNoise}:allf=u,format=rgb24" -q:v "$((100-quality))"\
         "$workingFile" -y -loglevel panic &>/dev/null; }

   # Main loop for repeated rotations and saves
   for (( i=0;i<"$rotations";i++ ))
   {
      # Save each full rotation as a new frame (if enabled)
      [[ "$saveInterim" != "no" ]] && [[ "$(( 10#$i%4 ))" -lt 1  ]] \
      && cp "$workingFile" "$(printf %07d $((i/4)))_$workingFile"

      # Rotate by 90 degrees and save, using whichever function chosen
      [[ "$version" == "IM" ]] \
      && rotateImageMagick \
      || rotateFFmpeg

      # Display progress
      displayRotation "$i" "$rotations"
   }
}

# Simple textual feedback of progress shown in terminal
function displayRotation() { clear;
   case "$(( 10#$1%4 ))" in
   3) printf "Total: $2 / Processing: $1 πŸ‘„  ";;
   2) printf "Total: $2 / Processing: $1 πŸ‘‡  ";;
   1) printf "Total: $2 / Processing: $1 πŸ‘†  ";;
   0) printf "Total: $2 / Processing: $1 πŸ‘…  ";;
   esac
}

# Driver function
function main { rotateDegrade "$@"; echo; }; main "$@"
download: rotateDegrade.sh

python version: https://oioiiooixiii.blogspot.com/2014/08/jpeg-destruction-via-repeated-rotate.html
original image: https://www.flickr.com/photos/flowizm/19148678846/ (CC BY-NC-SA 2.0)

parameters for top image, left to right:
original | rotations=300,quality=52,version=IM | rotations=200,quality=91,version=FFmpeg,noise=7

parameters for bottom image, left to right:
rotations=208,quality=91,version=FFmpeg,noise=7 | rotations=300,quality=52,version=FFmpeg,noise=0 | rotations=500,quality=74,version=IM | rotations=1000,quality=94,version=FFmpeg,noise=7 | rotations=300,quality=94,version=FFmpeg,noise=16

ImageMagick: Reversible Image Masking for lossy image formats



A demonstration of an image masking procedure using ImageMagick, presented as a Bash script. The concept is to obfuscate an image such that it becomes meaningless to the observer (human or machine) but that can be easily recovered using the correct steps. Since it is based on visual alterations rather than altering the file itself, it does not suffer from informational corruption if the image is resaved and/or resized.

In its current form, it's deemed as weakly cryptographic. It could be reversed via brute force study of patterns and edges (as was seen with VideoCrypt, the analogue video encryption used with satellite television¹). Image masking is an old idea that may still have some value today. Further notes are found within the bash script below.
#!/bin/bash
# Demo implementation of reversible image obfuscation for lossy file formats 
# (jpeg), based on ImageMagick[6] command chains.
#
# USAGE: imageMask.sh ['hide'/'recover'] ['image']
#        (Images cropped to multiples of 64 in this implementation.)
#
# * See 'NOTES' at bottom of script for further information and ideas.
#
# N.B. Regarding cryptography: reversible by brute force, edge-analysis ,etc.
# Designed for privacy from casual scanning (human/machine). Inspired by
# previously developed image masking systems: (GMask, JMask, VideoCrypt, etc.)
#
# Source: https://oioiiooixiii.blogspot.com
# Version: 2018.02.19.05.02.27

function obsfucate() # Takes: 'filename', 'width', and 'height'
{
   local width="$2" height="$3"
   
   # Crop into 64x64 blocks, rotate 90 degrees, and negate 1/4.
   # Tile blocks in reversed image orientation (Height x Width).
   # Crop into 16x16 blocks, rotate 90 degrees, and negative 1/4.
   # Tile blocks in reversed image orientation (Height x Width).
   # One extra 'rotate' at end to return to original orientation.
   
   convert "$1" -crop 64x64 -rotate 90  \
      \( +repage -region 32x32+0+0 -negate \) miff:- \
   | montage miff:- -mode concatenate \
      -tile "$((height/64))"x"$((width/64))" miff:- \
   | convert miff:- -crop 16x16 -rotate 90 \
      \( +repage -region 8x8+0+0 -negate \)  miff:- \
   | montage miff:- -mode concatenate \
      -tile "$((height/16))"x"$((width/16))" miff:- \
   | convert miff:- -rotate 90 ${1%.*}_HIDDEN.jpg
   
}

function deobfuscate() # Takes: 'filename', 'width', and 'height'
{
   local width="$3" height="$2"
   # width,height values swapped, 270 rotate to match 'obfuscate' re-orientation
   
   convert "$1" -rotate 270 -crop 64x64 -rotate 270 \
      \( +repage -region 32x32+0+32 -negate \) miff:- \
   | montage miff:- -mode concatenate \
      -tile "$((height/64))"x"$((width/64))"  miff:- \
   | convert miff:- -crop 16x16 -rotate 270 \
      \( +repage -region 8x8+8+8 -negate \) miff:- \
   | montage miff:- -mode concatenate \
      -tile "$((height/16))"x"$((width/16))" ${1%.*}_RECOVERED.jpg
}

function main()
{
   local width="$(identify -format "%w" "$2")"
   local height="$(identify -format "%h" "$2")"    

   # Crude method of making the image dimensions multiples of 64
   if [[ "$((width%64))" -gt 0 || "$((height%64))" -gt 0 ]]
   then
      local width="$(((width/64)*64))"
      local height="$(((height/64)*64))"
      convert "$2" -crop "$width"x"$height"+0+0 +repage "${2%.*}_CROPPED.png"
      local filename="${2%.*}_CROPPED.png"
   fi

   [[ "$1" == "hide" ]] && obsfucate "${filename:-$2}" "$width" "$height"
   [[ "$1" == "recover" ]] && deobfuscate "${filename:-$2}" "$width" "$height"
}

main "$@"
exit

### NOTES ###################################################################

# The command chain 'algorithm' demonstrated here, is just one particular way of
# rearranging an image, using rotation, negation, and altering aspect ratios. 
# More complex chaining as well as extra measures will result in more obscurity.
# Saving files at interim stage and reordering blocks allows for greater 
# manipulation and security (e.g. unique block ordering based on pass phrases). 
#
# Advantages and uses: survives rescaling and re-compression, with minimal 
# additional losses due to principles of DCT quantisation. It allows for images 
# to be stored on-line using public/private 'cloud' services that destroy 
# cryptographic information by rescaling/compressing the image. Reversible via 
# alternate means (e.g. Python PIL etc.) if software becomes unavailable. 
# Cons: Relatively slow, cumbersome, non-dynamic way to browse images.
#
# A side-effect of the procedure is the removal of EXIF information from the 
# image, thus no need for including the '-strip' argument such was desired.
download: imageMask.sh




To show the differences created when the image is resaved [with heavy compression] while in a state of obfuscation, the following was completed: The image was masked and saved as a jpeg with quality set to '25'. The original image was also saved as a jpeg with quality set to '25'. Both of these images were 'differenced' with the original, and the result of each were 'differenced' with each other. This image was then normailised for clarity. N.B. "Difference" does not imply quality loss but variance in compression artifacting.

Below left: Differences between original and image that underwent obfuscation then deobfuscation.
Below right: Differences [normalised] between heavily compressed images, as mentioned above.



more info: http://gmask.awardspace.info/
see also: http://oioiiooixiii.blogspot.com/2014/03/a-novel-approach-to-encrypting-images.html
related: http://oioiiooixiii.blogspot.com/2014/11/illegal-pixels.html
related: https://oioiiooixiii.blogspot.com/2015/05/creating-two-images-from-one-set-of.html
related: https://oioiiooixiii.blogspot.com/2015/06/gimpmask-encrypting-all-or-part-of-image.html
image source: c⃠  https://images.nasa.gov/details-201409250001HQ.html
image source: c⃠  https://commons.wikimedia.org/wiki/File:Moscow,_Krasnaya_Square,_Sunset.jpg

¹ http://www.cl.cam.ac.uk/~mgk25/tv-crypt/image-processing/antisky.html
¹ http://www.techmind.org/vdc/
¹ https://guru.multimedia.cx/decrypting-videocrypt/

ImageMagick: Bidirectional repeated liquid-rescale (content aware scaling)



The use of heavy seam carving (liquid rescale/content aware scaling) in images and video has been well expressed on the internet over the past 10 years¹. The concept of repeated "bidirectional" seam carving has been demonstrated here numerous times in the past².

The concept of bidirectional carving is to resize the image only slightly, and return it to its original dimensions. If this is done repeatedly many times (hundreds or thousands of iterations) the image will continue to corrupt and evolve in novel ways. A simple bash script used for automating the process is presented below.
#!/bin/bash

# Repeated bidirectional 'seam carving' on image. (Requires ImageMagick).
#  - Arguments: filename, iterations, size difference, quality, milestones.
#  - See 'NOTES' at bottom of script for further details
# ver: 2017.11.15.13.07.17
# source: https://oioiiooixiii.blogspot.com

function main()
{   
   # Make duplicate file for working on
   [ "$4" == "png" ] \
      && ext="png" \
      && quality="" \
      || quality="-format jpg -quality $4"
   filename="${1}_lqr-i$2-s$3-q$4.${ext:-jpg}"
   convert "$1" $quality "$filename"
   
   # Set up scaling variables
   originalRes="$(identify $1 | cut -d' ' -f3)"
   pix="$3"
   altRes="$(( $(cut -dx -f1 <<<$originalRes)+pix ))x\
           $(( $(cut -dx -f2 <<<$originalRes)+pix ))"

   #main loop
   for ((i=0;i<"$2";i++)) 
   {
      clear
      printf "FILE: $filename\nFRAMES: $((frame))\nITERATION: $((i+1))\n"
      printf "* Scaling to alt. resolution '${altRes//[[:space:]]/}'\n"
      mogrify -liquid-rescale "$altRes!" $quality "$filename"
      printf "* Scaling to original resolution '$originalRes'\n"
      mogrify -liquid-rescale "$originalRes!" $quality "$filename" 
      
      # Create a new image at milestone interval, if set
      [ ! -z "$5" ] && ! (( $i % $5 )) \
         && cp "$filename" "$((frame++))_$filename"
   }
}

main "$@"
exit

### NOTES ######################################################################
# $1=filename - Name/location of image.
# $2=iterations - The total number of desired resizes.
# $3=size difference - Amount of pixels to scale by (positive or negative).
# $4=quality - Set desired jpeg quality or 'png' (compression causes entropy).
# $5=milestones - Create new file at specified interval, capturing current state
# Possible script improvement: File i/o location in ram drive /dev/shm/ etc.
download: imageMagick_bidirectional-seam-carve.sh



The process can develop numerous types of effect, depending on the attributes given. Shapes can become angular, or rudimentary. Sections of the image can begin to develop seams that tear and germinate. Eventually, most images cascade down into a mess of chaos that never resolves.

The example above demonstrates some of the effects different arguments used in the script can have on an image, though it is in no way exhaustive, nor shows the extremities of the effect. For a more extreme example, see the video below. The starting image size was 960x408, and the arguments given at run-time were: 10,000 iterations, size reduction of 100 pixels, jpeg quality of 90, and every frame saved.


¹ info: http://knowyourmeme.com/memes/content-aware-scaling
² related: https://oioiiooixiii.blogspot.com/search/label/Seam%20Carving
source video: https://en.wikipedia.org/wiki/The_Doctor_and_the_Devils

JPEG destruction: 0° vs. 90° vs. 180°



As a follow-up to an experiment involving the degradation of images, seen when rotated in Microsoft's "Image and Fax Viewer", a program was devised to investigate how much of the degradation was caused by the rotation. The image above illustrates that merely saving the image repeatedly, is not conducive to the deterioration. Also interesting is the slight, but discernible, effect 180° rotations have in the process.

context: http://oioiiooixiii.blogspot.com/2014/08/the-effects-of-rotating-images-in.html

The program was originally completed using Processing, but this proved cumbersome as it was unable to handle so many I/O file operations. A program to rotate and save images was later rewritten in Python, which was not hampered by any such issues.

context: http://oioiiooixiii.blogspot.com/2014/08/jpeg-destruction-via-repeated-rotate.html

Content from this blog post was originally published in February 2013

Taking an Image on a Journey

A mosaic showing the degradation, via seam-carve iterations, of an image of Polish model, Anna JagodziΕ„ska.


The result of 4500 seam-carve iterations, high jpeg compression [ImageMagick Liquid Rescale].

model: Anna JagodziΕ„ska
image source: http://www.lazygirls.info/Anna_Jagodzinska/Y_Kei_2005_Fall_gerkmkN

JPEG destruction, via repeated rotate and saves - Python 3.0+ implementation



A previous blog post detailed experimentation with Windows XP's image viewer, to degrade jpeg images. Windows Vista/7/8 fixed this issue with the native viewer, so it no longer saves after each rotation. To implement the process in operating systems outside of Windows XP, a custom algorithm had to be formed. I tried implementing this with Processing, but it was cumbersome to achieve. In Python, using the PIL module, the procedure is trivial.

UPDATE: 2019.08.03

Having recently reviewed the original Python script included in this post, it was clear it could do with serious improvements. An updated, simpler and more logical version (with comments), has been included below. The original script can be found underneath it.

from PIL import Image

# Image file to rotate
filename = "image.jpg"

# Amount of times image is rotated
for i in range(0, 1000):
   # Open image file
   img = Image.open (filename)
   # Rotate file 90 degrees and save it
   img.rotate(90,expand=True).save(filename, "JPEG")
   # If image has rotated 4 times (360 degrees) save new image
   if (i % 4) == 0:
      img.save (str(i/4)+".jpg", "JPEG")

Old version:
from PIL import Image

# Keeps track of orientation
flipBack = 90

# Bootstrapping first image
img = Image.open("original.jpg")
img.rotate(270).save("0.jpg", "JPEG")

# Loads file, rotates, saves as new file
for i in range(0, 19):
    img = Image.open(str(i)+".jpg")
    img.rotate(270).save(str(i+1)+".jpg", "JPEG")

    # Rotates back to original orientation, saves over file
    img.rotate(flipBack).save(str(i)+".jpg", "JPEG")
    if flipBack <= 180:
        flipBack += 90
    else:
        flipBack = 0



related: http://oioiiooixiii.blogspot.ie/2014/08/the-effects-of-rotating-images-in.html

The effects of rotating images in Window XP


A few years ago, I experimented with Windows XP's "Image and Fax Viewer", to see what repeated rotations, numbering hundreds and thousands, would do to a jpeg image. Each rotation would cause the image to be automatically saved, which caused substantial degradation to the image. The procedure was automated by writing a vbscript which interacted with the image viewer. These are some images from the original tests.


Emilee Cox is a girl that once sent over 30,000 text messages from her phone in a month. I thought it only right to rotate her image the same amount of times, but as you can see from the results, she didn't even last for a thousand.

source: http://emileecox.blogspot.com/



Bill Gates proved a tough nut to crack. Still discernible after thirty thousand rotations.


With entropy increasing, so did file size, as more and more chaotic detail had to be compressed.

Contents of this blog post were originally published: March 05, 2009