Keith Vetter 2010-09-10 -- Mormon Porn or Nude Bubbling was created by UltimusMormon on the BodyBuilding.com forum. He states "Being mormon, I can't look at porn or nudity. So I have to get creative. That's why I invented 'bubbling' ".
The basic idea is to take an image of a woman with lots of bare skin, create a bunch of 'bubbles' on top of the bare skin and turn that into an image mask where only the bubbles are transparent. The effect is that your imagination turns an innocent, albeit racy image into a seemingly nude, pornographic picture. You don't see any naughty bits, but your mind imagines them.
For more info check out http://nudebubbling.com/ and http://bubbler.heroku.com/index.html .
The fun for me was solving the technical challenges, like how to you create transparent bubbles in a tk image? I had to write a primitive circle drawing routing. Also, I wanted have the program to be more interactive, creating and displaying the mask in real time, but I couldn't get it done fast enough.
##+########################################################################## # # mormonPorn # by Keith Vetter, Sept 8, 2010 # package require Tk package require Img package require http set S(fname) "" set S(transparent) 0 set B1 { bubbleInside yellow bubbleEdge black mask yellow width 0 width2 2 } set B2 { bubbleInside {} bubbleEdge magenta mask yellow width 4 width2 4 } array set B $B1 proc DoDisplay {} { global S wm title . "Mormon Porn" bind all <F2> {console show} menu .m -tearoff 0 . configure -menu .m menu .m.file -tearoff 0 menu .m.bubble -tearoff 0 menu .m.help -tearoff 0 .m add cascade -label "File" -menu .m.file -underline 0 .m add cascade -label "Bubble" -menu .m.bubble -underline 0 .m add cascade -label "Help" -menu .m.help -underline 0 .m.file add command -label "Restart" -command Restart -underline 0 .m.file add command -label "Open File" -command "Open file" -under 0 .m.file add command -label "Open URL" -command "Open url" -under 5 .m.file add command -label "Save" -command "Save" -under 0 .m.file add separator .m.file add command -label "Exit" -command exit -under 1 .m.bubble add checkbutton -label "Transparent bubbles" \ -underline 0 -variable ::S(transparent) -command ToggleTransparent .m.help add command -label "Demo" -command Demo -under 0 .m.help add command -label "About" -command About -under 0 ::ttk::frame .buttons ::ttk::button .mask -text "Make Mask" -command MakeMask ::ttk::frame .f -borderwidth 2 -relief ridge canvas .c -width 400 -height 600 -highlightthickness 0 -bd 0 -bg cyan bind .c <1> [list Button1 down %x %y] bind .c <B1-Motion> [list Button1 move %x %y] bind .c <ButtonRelease-1> [list Button1 up %x %y] bind .c <2> [list Button2 down %x %y] bind .c <B2-Motion> [list Button2 move %x %y] bind .c <ButtonRelease-2> [list Button2 up %x %y] bind .c <3> [list Button3 down %x %y] ::ttk::frame .f2 -borderwidth 2 -relief ridge canvas .c2 -width 400 -height 600 -highlightthickness 0 -bd 0 -bg cyan pack .buttons -side bottom -fill x pack .mask -in .buttons -side left -pady .2in -expand 1 pack .f -side left -fill both -expand 1 pack .c -in .f -side left -fill both -expand 1 pack .f2 -side left -fill both -expand 1 pack .c2 -in .f2 -side left -fill both -expand 1 } ##+########################################################################## # # NewBubbleImage -- Starts a new image to be bubbled # proc NewBubbleImage {fname} { global S BITMAP unset -nocomplain BITMAP foreach img [image names] { image delete $img } .c delete all .c2 delete all set img [NewImage $fname] set S(all) {} .c config -width $S(w) -height $S(h) .c2 config -width $S(w) -height $S(h) .c create image 0 0 -tag img -image $img -anchor nw .c2 create image 0 0 -tag img -image $img -anchor nw } ##+########################################################################## # # Restart -- Erases all bubbles # proc Restart {} { global S .c delete bubble set S(all) {} } ##+########################################################################## # # Open -- Gets new image filename or url # proc Open {what} { set fname [GetURLorFile $what] if {$fname eq ""} return NewBubbleImage $fname } ##+########################################################################## # # Save -- Saves our bubble image # proc Save {} { set types {{"JPEG files" {.jpg}} {"All Files" *}} set fname [tk_getSaveFile -title "Mormon Porn Save File" \ -defaultextension .jpg \ -initialdir [file dirname $::S(fname)] \ -filetypes $types] if {$fname eq ""} return image create photo ::img::save ::img::save copy ::img::p if {"::img::mask" in [image names]} { ::img::save copy ::img::mask } ::img::save write $fname -format jpeg image delete ::img::save tk_messageBox -message "Saved $fname" } ##+########################################################################## # # GetURLorFile -- Puts up either the stock getOpenFile dialog or custom # getUrl dialog, and returns either the filename or url # proc GetURLorFile {what} { global S if {$what eq "file"} { set types {{"Image files" {.gif .jpg .png}} {"All Files" *}} set fname [tk_getOpenFile -title "Mormon Porn Open File" \ -initialdir [file dirname $S(fname)] \ -initialfile [file tail $S(fname)] \ -filetypes $types] } else { set save $::S(url) destroy .ux toplevel .ux wm title .ux "Mormon Porn Get URI" wm protocol .ux WM_DELETE_WINDOW {set ::S(url) ""; destroy .ux} set x [expr {[winfo x .] + 50}] set y [expr {[winfo y .] + 50}] wm geom .ux "+$x+$y" ::ttk::frame .ux.u ::ttk::label .ux.u.l -text "Enter URL" ::ttk::entry .ux.u.e -textvariable ::S(url) -width 30 ::ttk::frame .ux.u.buttons ::ttk::button .ux.u.buttons.go -text "Open" -command {destroy .ux} ::ttk::button .ux.u.buttons.cancel -text "Cancel" \ -command {set ::S(url) ""; destroy .ux} pack .ux.u -fill both -expand 1 -padx .1in -pady {.1in 0} grid .ux.u.l -sticky w grid .ux.u.e -sticky ns grid .ux.u.buttons -pady .1in -sticky news pack .ux.u.buttons.go .ux.u.buttons.cancel -side left -expand 1 focus .ux.u.e .ux.u.e selection range 0 end .ux.u.e icursor end tkwait window .ux set fname $::S(url) if {$::S(url) eq ""} { set ::S(url) $save } } return $fname } ##+########################################################################## # # MakeBubble -- Creates a bubble on the screen # proc MakeBubble {xy} { global B set tag [.c create oval $xy \ -fill $B(bubbleInside) \ -outline $B(bubbleEdge) \ -width $B(width) \ -tag bubble] return $tag } ##+########################################################################## # # Button1 -- Handles creating and sizing a bubble # proc Button1 {verb x y} { global S B if {$verb eq "down"} { set S(cx) $x set S(cy) $y set xy [Box $x $y $x $y] set S(tag) [MakeBubble $xy] } elseif {$verb eq "move"} { set xy [Box $S(cx) $S(cy) $x $y] .c coords $S(tag) $xy set S(last) [list $x $y] } elseif {$verb eq "up"} { lappend S(all) [list $S(tag) $S(cx) $S(cy) $S(radius)] } } ##+########################################################################## # # Button2 -- Handles dragging a bubble around the screen # proc Button2 {verb x y} { global S B if {$verb eq "down"} { set S(who) [FindBubble $x $y] if {$S(who) == -1} return set id [lindex $S(all) $S(who) 0] .c itemconfig $id -width $B(width2) .c config -cursor fleur set S(cx) $x set S(cy) $y } elseif {$verb eq "move"} { if {$S(who) == -1} return set dx [expr {$x - $S(cx)}] set dy [expr {$y - $S(cy)}] if {$dx == 0 && $dy == 0} return set id [lindex $S(all) $S(who) 0] .c move $id $dx $dy lassign [lindex $S(all) $S(who)] . cx cy incr cx $dx incr cy $dy set S(cx) $x set S(cy) $y lset S(all) $S(who) 1 $cx lset S(all) $S(who) 2 $cy } elseif {$verb eq "up"} { if {$S(who) == -1} return set id [lindex $S(all) $S(who) 0] .c itemconfig $id -width $B(width) .c config -cursor {} } } ##+########################################################################## # # Button3 -- Handles deleting a bubble # proc Button3 {verb x y} { global S set idx [FindBubble $x $y] if {$idx == -1} return set id [lindex $S(all) $idx 0] .c delete $id set S(all) [lreplace $S(all) $idx $idx] } ##+########################################################################## # # FindBubble -- Searches all bubbles in S(all) and return index of the last one # which overlaps a given point # proc FindBubble {x y} { global S for {set i [expr {[llength $S(all)] - 1}]} {$i >= 0} {incr i -1} { lassign [lindex $S(all) $i] id cx cy radius if {hypot($cx-$x,$cy-$y) <= $radius} { return $i } } return -1 } ##+########################################################################## # # Box -- Given center and point on edge, returns bbox for our bubble # proc Box {cx cy x y} { set radius [expr {round(hypot($x-$cx,$y-$cy))}] if {$radius < 2} { set radius 2} set ::S(radius) $radius set x0 [expr {$cx-$radius}] set y0 [expr {$cy-$radius}] set x1 [expr {$cx+$radius}] set y1 [expr {$cy+$radius}] return [list $x0 $y0 $x1 $y1] } ##+########################################################################## # # MakeMask -- Creates our bubble mask by hand drawing filled circles into # an offscreen bitmap, then setting all empty bits to a mask color--leaving # on bits undefined/transparent # proc MakeMask {{clr ""}} { global S BITMAP B unset -nocomplain BITMAP if {$clr eq ""} { set clr $B(mask) } foreach bubble $S(all) { MakeCircleMask {*}$bubble } set start [clock seconds] ::img::mask blank .c2 create image 0 0 -tag mask -image ::img::mask -anchor nw for {set y 0} {$y < $S(h)} {incr y} { set y1 [expr {$y+1}] update set off 1 set BITMAP($S(w),$y) 1 ;# Sentinel for {set x 0} {$x <= $S(w)} {incr x} { if {! [info exists BITMAP($x,$y)]} { ;# Pixel that needs masking if {$off} { set off 0; set left $x} } else { ;# Pixel that needs transparency if {! $off} { set off 1 ::img::mask put $clr -to $left $y $x $y1 } } } if {! $off} { puts "bad off: $y" ; return} } set end [clock seconds] puts "[expr {$end-$start}] seconds" } ##+########################################################################## # # MakeCircleMask -- "Draws" a circle into a bitmap # see http://www.cs.unc.edu/~mcmillan/comp136/Lecture7/circle.html # proc MakeCircleMask {tag cx cy r} { set x 0 set y $r set p [expr {(5 - $r*4)/4.0}] _ExploitSymmetry $cx $cy $x $y while {$x < $y} { incr x if {$p < 0} { set p [expr {$p + 2*$x + 1}] } else { incr y -1 set p [expr {$p + 2*($x-$y)+1}] } _ExploitSymmetry $cx $cy $x $y } } ##+########################################################################## # # _ExploitSymmetry -- Uses symmetry to reflect known edge point around the circle # proc _ExploitSymmetry {cx cy x y} { if {$x == 0} { _Line [expr {$cx+$y}] $cy [expr {$cx-$y}] $cy } elseif {$x == $y} { _Line [expr {$cx + $x}] [expr {$cy + $y}] [expr {$cx - $x}] [expr {$cy + $y}] _Line [expr {$cx + $x}] [expr {$cy - $y}] [expr {$cx - $x}] [expr {$cy - $y}] } elseif {$x < $y} { _Line [expr {$cx + $x}] [expr {$cy + $y}] [expr {$cx - $x}] [expr {$cy + $y}] _Line [expr {$cx + $x}] [expr {$cy - $y}] [expr {$cx - $x}] [expr {$cy - $y}] _Line [expr {$cx + $y}] [expr {$cy + $x}] [expr {$cx - $y}] [expr {$cy + $x}] _Line [expr {$cx + $y}] [expr {$cy - $x}] [expr {$cx - $y}] [expr {$cy - $x}] } } ##+########################################################################## # # _Line -- Fills in all bits on line between x0,y and x1,y # proc _Line {x0 y0 x1 y1} { global BITMAP S # ASSERTION: $y0 == $y1 if {$y0 < 0 || $y0 >= $S(h)} return set lo [expr {max(min($x0,$x1),0)}] set hi [expr {min(max($x0,$x1),$S(w)-1)}] for {set x $lo} {$x <= $hi} {incr x} { set BITMAP($x,$y0) 1 } #.c create line $x0 $y0 $x1 $y1 -fill cyan } ##+########################################################################## # # Demo -- Does a quick demo using canned bubbles on a stock image # proc Demo {} { global demo set demo(who) [expr {($demo(who) + 1) % [llength [array names demo *,url]]}] if {$::S(proto) eq "http" && $::S(url) eq $demo($demo(who),url)} { Restart } else { NewBubbleImage $demo($demo(who),url) } foreach bubble $demo($demo(who),bubbles) { lassign $bubble tag cx cy r set x [expr {$cx + $r}] set y $cy set xy [Box $cx $cy $x $y] set tag [MakeBubble $xy] lappend ::S(all) [list $tag $cx $cy $::S(radius)] } MakeMask } ##+########################################################################## # # About -- Our about and help dialog # proc About {} { destroy .about toplevel .about wm title .about "About Mormon Porn" wm geom .about "+[expr {[winfo x .] + [winfo width .] + 10}]+[winfo y .]" text .about.t -relief raised -wrap word -width 70 -height 24 .about.t config -padx 10 -pady 10 button .about.dismiss -text Dismiss -command {destroy .about} pack .about.dismiss -side bottom -pady 10 pack .about.t -side top -expand 1 -fill both set bold "[font actual [.about.t cget -font]] -weight bold" set italic "[font actual [.about.t cget -font]] -slant italic" .about.t tag configure title -justify center -foreground red \ -font "Times 20 bold" .about.t tag configure title2 -justify center -font "Times 12 bold" .about.t tag configure bold -font $bold .about.t tag configure quote -font $italic .about.t tag configure n -lmargin1 15 -lmargin2 15 .about.t insert end "Mormon Porn / Nude Bubbling\n" title "by Keith Vetter\n\n" title2 .about.t insert end "Mormon Porn was created by UltimusMormon " .about.t insert end "on the BodyBuilding.com forum. He states \"" .about.t insert end "Being mormon, I can't look at porn or nudity. " quote .about.t insert end "So I have to get creative. That's why I " quote .about.t insert end "invented 'bubbling'" quote "\".\n\n" set msg "The basic idea is to take an image of a woman with " append msg "lots of bare skin, create a bunch of 'bubbles' " append msg "on top of the bare skin and turn that into an image mask where " append msg "only the bubbles are transparent.\n\n" .about.t insert end $msg set msg "The effect is that your imagination turns an innocent, " append msg "albeit racy image " append msg "into a seemingly nude, pornographic picture. " append msg "You don't see any naughty bits, but your mind imagines them.\n\n" .about.t insert end $msg .about.t insert end "For more info check out " .about.t insert end "http://nudebubbling.com/" bold " and " .about.t insert end "http://bubbler.heroku.com/index.html" bold ".\n\n" .about.t insert end "HOW TO USE\n" bold .about.t insert end "o" n " Create bubble: click and drag\n" .about.t insert end "o" n " Move bubble: click and drag middle button\n" .about.t insert end "o" n " Delete bubble: click right button\n" .about.t config -state disabled } ##+########################################################################## # # NewImage -- Creates a new image from a filename or url # proc NewImage {fname} { global S if {[file exists $fname]} { set ::S(fname) $fname set ::S(proto) file set img [image create photo ::img::p -file $fname] set S(w) [image width $img] set S(h) [image height $img] image create photo ::img::mask -width $S(w) -height $S(h) return $img } if {[string match "http:*" $fname]} { set token [http::geturl $fname] ::http::wait $token set code [::http::code $token] set ncode [::http::ncode $token] set data [::http::data $token] ; list ::http::cleanup $token if {$ncode != 200} { error "Can't download $fname\n$code" } set n [catch { set img [image create photo ::img::p -data $data] } emsg] if {$n} {error "Not an image: $fname"} set ::S(url) $fname set ::S(proto) http set S(w) [image width $img] set S(h) [image height $img] image create photo ::img::mask -width $S(w) -height $S(h) return $img } error "can't find $fname" } ##+########################################################################## # # ToggleTransparent -- As the name says # proc ToggleTransparent {} { global S B puts "S(transparent): $S(transparent)" if {$S(transparent)} { array set B $::B2 } else { array set B $::B1 } .c itemconfig bubble -width $B(width) -fill $B(bubbleInside) -outline $B(bubbleEdge) } ################################################################ set demo(who) -1 set demo(0,url) {http://upload.wikimedia.org/wikipedia/commons/thumb/5/50/Lovely_model_-_Tricia_from_Singapore_-Bikini.jpg/400px-Lovely_model_-_Tricia_from_Singapore_-Bikini.jpg} set demo(0,bubbles) {{bubble0 191 90 82} {bubble1 310 175 53} {bubble6 209 300 70} {bubble9 311 450 81} {bubble12 43 391 47} {bubble13 54 118 47} {bubble14 57 530 52} {bubble15 81 255 41} {bubble16 142 206 30} {bubble18 186 469 49}} set demo(1,url) http://lh5.ggpht.com/_D8FfQDqFKYI/SsNN2pCfBvI/AAAAAAAAACs/Iw_Fo-JKkd4/s640/bikini_skirt01.jpg set demo(1,bubbles) {{3 144 93 93} {4 277 165 50} {5 390 358 79} {6 154 435 79} {8 154 279 37} {9 36 282 49} {10 307 535 65}} set demo(2,url) http://upload.wikimedia.org/wikipedia/commons/thumb/2/26/Christy_Marie_as_Slave_Leia.jpg/398px-Christy_Marie_as_Slave_Leia.jpg set demo(2,bubbles) {{14 134 51 124} {15 332 169 70} {16 340 340 42} {19 207 418 61} {20 336 519 59} {21 80 546 64} {22 83 404 43} {23 101 220 43}} DoDisplay NewBubbleImage $demo(0,url) return