Created
November 5, 2020 18:10
-
-
Save nikolay-n/a0f8b5d645ba74a3f30655790550496b to your computer and use it in GitHub Desktop.
Screen glitch example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python2.7 | |
# -*- coding: utf-8 -*- | |
import Foundation | |
import AppKit | |
import WebKit | |
import Quartz | |
import ctypes | |
from PyObjCTools import AppHelper | |
from objc import _objc, nil, super, pyobjc_unicode, registerMetaDataForSelector | |
import shutil | |
import sys | |
import os | |
import tempfile as tmp | |
if len(sys.argv) != 2 or not os.path.exists(sys.argv[1]): | |
print("Usage: python2.7 ./glitch.py /path/to/image"); | |
sys.exit(1) | |
def hide_cursor(): | |
Quartz.CGAssociateMouseAndMouseCursorPosition(False) | |
Quartz.CGDisplayHideCursor(Quartz.CGMainDisplayID()) | |
def show_cursor(): | |
Quartz.CGAssociateMouseAndMouseCursorPosition(True) | |
Quartz.CGDisplayShowCursor(Quartz.CGMainDisplayID()) | |
class ProcessSerialNumber(ctypes.Structure): | |
_fields_ = [ | |
('highLongOfPSN', ctypes.c_uint32), | |
('lowLongOfPSN', ctypes.c_uint32), | |
] | |
kNoProcess = 0 | |
kSystemProcess = 1 | |
kCurrentProcess = 2 | |
kProcessTransformToForegroundApplication = 1 | |
kProcessTransformToBackgroundApplication = 2 | |
kProcessTransformToUIElementAppication = 4 | |
appSrvc = ctypes.CDLL('/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices') | |
appSrvc.TransformProcessType.argtypes = [ctypes.POINTER(ProcessSerialNumber), ctypes.c_uint32] | |
appSrvc.SetFrontProcess.argtypes = [ctypes.POINTER(ProcessSerialNumber)] | |
psn = ProcessSerialNumber(0, kCurrentProcess) | |
appSrvc.TransformProcessType(psn, kProcessTransformToForegroundApplication) | |
appSrvc.SetFrontProcess(psn) | |
sharedApp = AppKit.NSApplication.sharedApplication() | |
currentApp = AppKit.NSRunningApplication.currentApplication() | |
# hide from Dock | |
sharedApp.setActivationPolicy_(AppKit.NSApplicationActivationPolicyAccessory) | |
bundle = AppKit.NSBundle.mainBundle() | |
info = bundle.localizedInfoDictionary() or bundle.infoDictionary() | |
info['CFBundleName'] = 'WebKit' | |
info['NSAppTransportSecurity'] = {'NSAllowsArbitraryLoads': Foundation.YES} | |
_objc_so = ctypes.cdll.LoadLibrary(_objc.__file__) | |
# Bridgesupport metadata for [WKWebView evaluateJavaScript:completionHandler:] | |
_eval_js_metadata = { 'arguments': { 3: { 'callable': { 'retval': { 'type': b'v' }, 'arguments': { 0: { 'type': b'^v' }, 1: { 'type': b'@' }, 2: { 'type': b'@' }}}}}} | |
class MainWindow(AppKit.NSWindow): | |
def canBecomeKeyWindow(self): | |
return True | |
def canBecomeMainWindow(self): | |
return False | |
rect = AppKit.NSScreen.mainScreen().frame() | |
window_mask = AppKit.NSBorderlessWindowMask | |
window = MainWindow.alloc().\ | |
initWithContentRect_styleMask_backing_defer_(rect, window_mask, AppKit.NSBackingStoreBuffered, False).retain() | |
window.setAnimationBehavior_(AppKit.NSWindowAnimationBehaviorNone) | |
frame = window.frame() | |
frame.size.width = rect.size.width | |
frame.size.height = rect.size.height | |
window.setFrame_display_(frame, True) | |
webkit = WebKit.WKWebView.alloc().initWithFrame_(rect).retain() | |
window.setOpaque_(False) | |
window.setHasShadow_(False) | |
window.setBackgroundColor_(AppKit.NSColor.colorWithSRGBRed_green_blue_alpha_(0, 0, 0, 1)) | |
window.setAlphaValue_(0.0) | |
window.setLevel_(AppKit.NSScreenSaverWindowLevel + 1) | |
window.setCollectionBehavior_(1 << 7) # NSWindowCollectionBehaviorFullScreenPrimary | |
try: | |
webkit.evaluateJavaScript_completionHandler_('', lambda a, b: None) | |
except TypeError: | |
registerMetaDataForSelector(b'WKWebView', b'evaluateJavaScript:completionHandler:', _eval_js_metadata) | |
config = webkit.configuration() | |
try: | |
config.preferences().setValue_forKey_(Foundation.NO, 'backspaceKeyNavigationEnabled') | |
config.preferences().setValue_forKey_(Foundation.YES, 'allowFileAccessFromFileURLs') | |
except Exception as e: | |
pass | |
tmp_dir = tmp.mkdtemp() | |
index_path = os.path.join(tmp_dir, "index.html") | |
image_file = sys.argv[1] | |
image_name = os.path.basename(image_file) | |
image_path = os.path.join(tmp_dir, image_name) | |
shutil.copy(image_file, image_path) | |
index_url = Foundation.NSURL.fileURLWithPath_(index_path) | |
base_url = Foundation.NSURL.fileURLWithPath_(tmp_dir) | |
content='''<html> | |
<head> | |
<style> | |
html, body { | |
padding:0px; | |
margin:0px; | |
} | |
body { | |
width:100%; | |
height:100%; | |
overflow: hidden; | |
background: #000; | |
color:#fff; | |
font-family: Monaco; | |
font-size:11pt; | |
-webkit-user-select: none; | |
user-select: none; | |
cursor: default; | |
} | |
#gl { | |
width: 100%; | |
height:100%; | |
} | |
</style> | |
<script> | |
var fps = 40; | |
const defaultShaderType = [ | |
'VERTEX_SHADER', | |
'FRAGMENT_SHADER', | |
]; | |
const screenShot = "__IMAGE__"; | |
const vertexSrc =` | |
attribute vec4 a_position; | |
attribute vec2 a_texcoord; | |
//uniform vec2 u_resolution; | |
varying vec2 v_texcoord; | |
void main() { | |
gl_Position = a_position; | |
v_texcoord = a_texcoord; | |
} | |
`; | |
const fragmentSrc=` | |
precision mediump float; | |
varying vec2 v_texcoord; | |
uniform sampler2D u_texture; | |
uniform vec2 u_resolution; | |
uniform float u_time; | |
uniform float random_seed; | |
const float glitch_size = .9; | |
const vec2 offset = vec2(0.0, 0.0); | |
const float glitch_horizontal = 0.1; | |
const float glitch_vertical = 0.01; | |
float rand(vec2 co){ | |
return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); | |
} | |
void main() { | |
vec2 xy = gl_FragCoord.xy / u_resolution.xy; | |
vec4 original = texture2D(u_texture, v_texcoord); | |
vec4 resultColor; | |
bool shifted = false; | |
float randSeed = (random_seed == 0.0) ? u_time : random_seed; | |
vec2 random; | |
float local_glitch_size = glitch_size; | |
float random_offset = 0.0; | |
if (local_glitch_size > 0.0) { | |
random.x = rand(vec2(floor(random_offset + xy.y / local_glitch_size) * local_glitch_size, randSeed)); | |
random.y = rand(vec2(floor(random_offset + xy.x / local_glitch_size) * local_glitch_size, randSeed)); | |
} else { | |
random.x = rand(vec2(xy.x, randSeed)); | |
random.y = rand(vec2(xy.y, randSeed)); | |
} | |
vec2 shift; | |
// if doing a horizontal glitch do a random shift | |
if ((random.x < glitch_horizontal)&&(random.y < glitch_vertical)) { | |
shift = (offset / u_resolution - 0.5); | |
shift = shift * rand(shift + random); | |
xy.x = mod(xy.x + random.x, 1.0); | |
xy.y = mod(xy.y + random.y, 1.0); | |
xy = xy + shift; | |
shifted = true; | |
} | |
else if (random.x < glitch_horizontal) { | |
shift = (offset / u_resolution - 0.5); | |
shift = shift * rand(shift + random); | |
xy = mod(xy + vec2(0.0, random.x) + shift, 1.0); | |
shifted = true; | |
} | |
else if (random.y < glitch_vertical) { | |
shift = (offset / u_resolution - 0.5); | |
shift = shift * rand(shift + random); | |
xy = mod(xy + vec2(random.y, 0.0) + shift, 1.0); | |
shifted = true; | |
} | |
resultColor = texture2D(u_texture, xy); | |
//resultColor = max(resultColor, original); | |
int rnd = int(floor(12.0 * rand(vec2(randSeed, resultColor.g)))); | |
if (rnd == 1) { | |
resultColor = original * resultColor ; | |
resultColor = abs(original - resultColor) / resultColor * original - 0.5; | |
float r=rand(vec2(randSeed, resultColor.r)); | |
float g=rand(vec2(randSeed, resultColor.g)); | |
float b=rand(vec2(randSeed, resultColor.b)); | |
resultColor = vec4(r ,g, b ,1.0); | |
//resultColor = vec4(0.,0.,0.,1.0); | |
}else if (rnd == 3 ){ | |
//resultColor = min(resultColor, original) - 0.5; | |
}else if (rnd == 4 ){ | |
//resultColor = abs(resultColor - original); | |
}else if (rnd == 5 ){ | |
resultColor = max(resultColor, original); | |
} | |
gl_FragColor = resultColor; | |
} | |
`; | |
window.addEventListener('DOMContentLoaded', (event) => main()); | |
window.addEventListener('click', (event) => postMessage("exit")); | |
document.addEventListener('contextmenu', event => event.preventDefault()); // disable right mouse click | |
function main(){ | |
var canvas = document.querySelector("#gl"); | |
var gl = canvas.getContext("webgl"); | |
var program = createProgramFromSources(gl, [vertexSrc, fragmentSrc]); | |
var positionLocation = gl.getAttribLocation(program, "a_position"); | |
var texcoordLocation = gl.getAttribLocation(program, "a_texcoord"); | |
var textureLocation = gl.getUniformLocation(program, "u_texture"); | |
var seedLocation = gl.getUniformLocation(program, "random_seed"); | |
var resolutionUniformLocation = gl.getUniformLocation(program, "u_resolution"); | |
var timeUniformLocation = gl.getUniformLocation(program, "u_time"); | |
var positionBuffer = gl.createBuffer(); | |
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); | |
var positions = [ | |
-1, -1, | |
-1, 1, | |
1, -1, | |
1, -1, | |
-1, 1, | |
1, 1, | |
]; | |
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW); | |
var texcoordBuffer = gl.createBuffer(); | |
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer); | |
var texcoords = [ | |
0, 0, | |
0, 1, | |
1, 0, | |
1, 0, | |
0, 1, | |
1, 1, | |
]; | |
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texcoords), gl.STATIC_DRAW); | |
var texture = gl.createTexture(); | |
var img = new Image(); | |
var random_seed = 0.0; | |
img.addEventListener('load', function() { | |
gl.bindTexture(gl.TEXTURE_2D, texture); | |
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true) | |
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); | |
requestAnimationFrame(render); | |
setTimeout(function(){ | |
postMessage("loaded"); | |
}, 100); | |
}); | |
img.src = screenShot; | |
function render(time) { | |
time *= 0.001; // convert to seconds | |
resizeCanvasToDisplaySize(gl.canvas); | |
// Tell WebGL how to convert from clip space to pixels | |
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); | |
gl.clear(gl.COLOR_BUFFER_BIT); | |
gl.bindTexture(gl.TEXTURE_2D, texture); | |
// Tell WebGL to use our shader program pair | |
gl.useProgram(program); | |
// Setup the attributes to pull data from our buffers | |
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); | |
gl.enableVertexAttribArray(positionLocation); | |
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0); | |
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer); | |
gl.enableVertexAttribArray(texcoordLocation); | |
gl.vertexAttribPointer(texcoordLocation, 2, gl.FLOAT, false, 0, 0); | |
gl.uniform1i(textureLocation, 0); | |
gl.uniform1f(timeUniformLocation, time); | |
gl.uniform1f(seedLocation, random_seed); | |
gl.uniform2f(resolutionUniformLocation, gl.canvas.width, gl.canvas.height); | |
gl.drawArrays(gl.TRIANGLES, 0, 6); | |
setTimeout(function(){ | |
requestAnimationFrame(render); | |
}, 1000/fps); | |
} | |
} | |
function resizeCanvasToDisplaySize(canvas, multiplier) { | |
multiplier = multiplier || 1; | |
const width = canvas.clientWidth * multiplier | 0; | |
const height = canvas.clientHeight * multiplier | 0; | |
if (canvas.width !== width || canvas.height !== height) { | |
canvas.width = width; | |
canvas.height = height; | |
return true; | |
} | |
return false; | |
} | |
function createProgramFromSources( | |
gl, shaderSources, opt_attribs, opt_locations, opt_errorCallback) { | |
const shaders = []; | |
for (let ii = 0; ii < shaderSources.length; ++ii) { | |
shaders.push(loadShader( | |
gl, shaderSources[ii], gl[defaultShaderType[ii]], opt_errorCallback)); | |
} | |
return createProgram(gl, shaders, opt_attribs, opt_locations, opt_errorCallback); | |
} | |
function createProgram( | |
gl, shaders, opt_attribs, opt_locations, opt_errorCallback) { | |
const errFn = opt_errorCallback || error; | |
const program = gl.createProgram(); | |
shaders.forEach(function(shader) { | |
gl.attachShader(program, shader); | |
}); | |
if (opt_attribs) { | |
opt_attribs.forEach(function(attrib, ndx) { | |
gl.bindAttribLocation( | |
program, | |
opt_locations ? opt_locations[ndx] : ndx, | |
attrib); | |
}); | |
} | |
gl.linkProgram(program); | |
// Check the link status | |
const linked = gl.getProgramParameter(program, gl.LINK_STATUS); | |
if (!linked) { | |
// something went wrong with the link | |
const lastError = gl.getProgramInfoLog(program); | |
errFn('Error in program linking:' + lastError); | |
gl.deleteProgram(program); | |
return null; | |
} | |
return program; | |
} | |
function loadShader(gl, shaderSource, shaderType, opt_errorCallback) { | |
const errFn = opt_errorCallback || error; | |
// Create the shader object | |
const shader = gl.createShader(shaderType); | |
// Load the shader source | |
gl.shaderSource(shader, shaderSource); | |
// Compile the shader | |
gl.compileShader(shader); | |
// Check the compile status | |
const compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS); | |
if (!compiled) { | |
// Something went wrong during compilation; get the error | |
const lastError = gl.getShaderInfoLog(shader); | |
errFn('*** Error compiling shader "' + shader + '":' + lastError); | |
gl.deleteShader(shader); | |
return null; | |
} | |
return shader; | |
} | |
function error(msg) { | |
if (window.console) { | |
if (window.console.error) { | |
window.console.error(msg); | |
} else if (window.console.log) { | |
window.console.log(msg); | |
} | |
} | |
} | |
function postMessage(message){ | |
window.webkit.messageHandlers.browserDelegate.postMessage(message); | |
} | |
</script> | |
</head> | |
<body> | |
<canvas id="gl"></canvas> | |
<div id="console"></div> | |
</body> | |
</html> | |
'''.replace("__IMAGE__", image_path) | |
with open(index_path, "w") as f: | |
f.write(content) | |
class BrowserDelegate(AppKit.NSObject): | |
def userContentController_didReceiveScriptMessage_(self, controller, message): | |
action = message.body() | |
if action == "loaded": | |
hide_cursor() | |
window.makeKeyAndOrderFront_(window) | |
currentApp.activateWithOptions_(AppKit.NSApplicationActivateIgnoringOtherApps) | |
window.setAlphaValue_(1.0) | |
window.makeFirstResponder_(webkit) | |
elif action == "exit": | |
show_cursor() | |
shutil.rmtree(tmp_dir, ignore_errors=True) | |
AppHelper.stopEventLoop() | |
browserDelegate = BrowserDelegate.alloc().init().retain() | |
config.userContentController().addScriptMessageHandler_name_(browserDelegate, 'browserDelegate') | |
webkit.loadFileURL_allowingReadAccessToURL_(index_url, base_url) | |
window.setContentView_(webkit) | |
window.makeFirstResponder_(webkit) | |
AppHelper.runEventLoop() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
To launch:
img=/tmp/screen.png; screencapture "$img"; python2.7 ./glitch.py "$img"; unlink "$img"