Plasma effect
You are encouraged to solve this task according to the task description, using any language you may know.
The plasma effect is a visual effect created by applying various functions, notably sine and cosine, to the color values of screen pixels. When animated (not a task requirement) the effect may give the impression of a colorful flowing liquid.
- Task
Create a plasma effect.
- See also
C++
Windows version. <lang cpp>
- include <windows.h>
- include <math.h>
- include <string>
const int BMP_SIZE = 240, MY_TIMER = 987654;
class myBitmap { public:
myBitmap() : pen( NULL ), brush( NULL ), clr( 0 ), wid( 1 ) {} ~myBitmap() { DeleteObject( pen ); DeleteObject( brush ); DeleteDC( hdc ); DeleteObject( bmp ); } bool create( int w, int h ) { BITMAPINFO bi; ZeroMemory( &bi, sizeof( bi ) ); bi.bmiHeader.biSize = sizeof( bi.bmiHeader ); bi.bmiHeader.biBitCount = sizeof( DWORD ) * 8; bi.bmiHeader.biCompression = BI_RGB; bi.bmiHeader.biPlanes = 1; bi.bmiHeader.biWidth = w; bi.bmiHeader.biHeight = -h;
HDC dc = GetDC( GetConsoleWindow() ); bmp = CreateDIBSection( dc, &bi, DIB_RGB_COLORS, &pBits, NULL, 0 ); if( !bmp ) return false;
hdc = CreateCompatibleDC( dc ); SelectObject( hdc, bmp ); ReleaseDC( GetConsoleWindow(), dc );
width = w; height = h; return true; } void clear( BYTE clr = 0 ) { memset( pBits, clr, width * height * sizeof( DWORD ) ); } void setBrushColor( DWORD bClr ) { if( brush ) DeleteObject( brush ); brush = CreateSolidBrush( bClr ); SelectObject( hdc, brush ); } void setPenColor( DWORD c ) { clr = c; createPen(); } void setPenWidth( int w ) { wid = w; createPen(); } void saveBitmap( std::string path ) { BITMAPFILEHEADER fileheader; BITMAPINFO infoheader; BITMAP bitmap; DWORD wb;
GetObject( bmp, sizeof( bitmap ), &bitmap ); DWORD* dwpBits = new DWORD[bitmap.bmWidth * bitmap.bmHeight];
ZeroMemory( dwpBits, bitmap.bmWidth * bitmap.bmHeight * sizeof( DWORD ) ); ZeroMemory( &infoheader, sizeof( BITMAPINFO ) ); ZeroMemory( &fileheader, sizeof( BITMAPFILEHEADER ) );
infoheader.bmiHeader.biBitCount = sizeof( DWORD ) * 8; infoheader.bmiHeader.biCompression = BI_RGB; infoheader.bmiHeader.biPlanes = 1; infoheader.bmiHeader.biSize = sizeof( infoheader.bmiHeader ); infoheader.bmiHeader.biHeight = bitmap.bmHeight; infoheader.bmiHeader.biWidth = bitmap.bmWidth; infoheader.bmiHeader.biSizeImage = bitmap.bmWidth * bitmap.bmHeight * sizeof( DWORD );
fileheader.bfType = 0x4D42; fileheader.bfOffBits = sizeof( infoheader.bmiHeader ) + sizeof( BITMAPFILEHEADER ); fileheader.bfSize = fileheader.bfOffBits + infoheader.bmiHeader.biSizeImage;
GetDIBits( hdc, bmp, 0, height, ( LPVOID )dwpBits, &infoheader, DIB_RGB_COLORS );
HANDLE file = CreateFile( path.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); WriteFile( file, &fileheader, sizeof( BITMAPFILEHEADER ), &wb, NULL ); WriteFile( file, &infoheader.bmiHeader, sizeof( infoheader.bmiHeader ), &wb, NULL ); WriteFile( file, dwpBits, bitmap.bmWidth * bitmap.bmHeight * 4, &wb, NULL ); CloseHandle( file );
delete [] dwpBits; } HDC getDC() const { return hdc; } DWORD* bits() { return ( DWORD* )pBits; }
private:
void createPen() { if( pen ) DeleteObject( pen ); pen = CreatePen( PS_SOLID, wid, clr ); SelectObject( hdc, pen ); } HBITMAP bmp; HDC hdc; HPEN pen; HBRUSH brush; void *pBits; int width, height, wid; DWORD clr;
}; class plasma { public:
plasma() { currentTime = 0; _WD = BMP_SIZE >> 1; _WV = BMP_SIZE << 1; _bmp.create( BMP_SIZE, BMP_SIZE ); _bmp.clear(); plasma1 = new BYTE[BMP_SIZE * BMP_SIZE * 4]; plasma2 = new BYTE[BMP_SIZE * BMP_SIZE * 4]; int i, j, dst = 0; double temp; for( j = 0; j < BMP_SIZE * 2; j++ ) { for( i = 0; i < BMP_SIZE * 2; i++ ) { plasma1[dst] = ( BYTE )( 128.0 + 127.0 * ( cos( ( double )hypot( BMP_SIZE - j, BMP_SIZE - i ) / 64.0 ) ) ); plasma2[dst] = ( BYTE )( ( sin( ( sqrt( 128.0 + ( BMP_SIZE - i ) * ( BMP_SIZE - i ) + ( BMP_SIZE - j ) * ( BMP_SIZE - j ) ) - 4.0 ) / 32.0 ) + 1 ) * 90.0 ); dst++; } } } void update() { DWORD dst; BYTE a, c1,c2, c3; currentTime += ( double )( rand() % 2 + 1 );
int x1 = _WD + ( int )( ( _WD - 1 ) * sin( currentTime / 137 ) ), x2 = _WD + ( int )( ( _WD - 1 ) * sin( -currentTime / 75 ) ), x3 = _WD + ( int )( ( _WD - 1 ) * sin( -currentTime / 125 ) ), y1 = _WD + ( int )( ( _WD - 1 ) * cos( currentTime / 123 ) ), y2 = _WD + ( int )( ( _WD - 1 ) * cos( -currentTime / 85 ) ), y3 = _WD + ( int )( ( _WD - 1 ) * cos( -currentTime / 108 ) );
int src1 = y1 * _WV + x1, src2 = y2 * _WV + x2, src3 = y3 * _WV + x3; DWORD* bits = _bmp.bits(); for( int j = 0; j < BMP_SIZE; j++ ) { dst = j * BMP_SIZE; for( int i= 0; i < BMP_SIZE; i++ ) { a = plasma2[src1] + plasma1[src2] + plasma2[src3]; c1 = a << 1; c2 = a << 2; c3 = a << 3; bits[dst + i] = RGB( c1, c2, c3 ); src1++; src2++; src3++; } src1 += BMP_SIZE; src2 += BMP_SIZE; src3 += BMP_SIZE; } draw(); } void setHWND( HWND hwnd ) { _hwnd = hwnd; }
private:
void draw() { HDC dc = _bmp.getDC(), wdc = GetDC( _hwnd ); BitBlt( wdc, 0, 0, BMP_SIZE, BMP_SIZE, dc, 0, 0, SRCCOPY ); ReleaseDC( _hwnd, wdc ); } myBitmap _bmp; HWND _hwnd; float _ang; BYTE *plasma1, *plasma2; double currentTime; int _WD, _WV;
}; class wnd { public:
wnd() { _inst = this; } int wnd::Run( HINSTANCE hInst ) { _hInst = hInst; _hwnd = InitAll(); SetTimer( _hwnd, MY_TIMER, 15, NULL ); _plasma.setHWND( _hwnd ); ShowWindow( _hwnd, SW_SHOW ); UpdateWindow( _hwnd ); MSG msg; ZeroMemory( &msg, sizeof( msg ) ); while( msg.message != WM_QUIT ) { if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) != 0 ) { TranslateMessage( &msg ); DispatchMessage( &msg ); } } return UnregisterClass( "_MY_PLASMA_", _hInst ); }
private:
void wnd::doPaint( HDC dc ) { _plasma.update(); } void wnd::doTimer() { _plasma.update(); } static int WINAPI wnd::WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam ) { switch( msg ) { case WM_PAINT: { PAINTSTRUCT ps; _inst->doPaint( BeginPaint( hWnd, &ps ) ); EndPaint( hWnd, &ps ); return 0; } case WM_DESTROY: PostQuitMessage( 0 ); break; case WM_TIMER: _inst->doTimer(); break; default: return DefWindowProc( hWnd, msg, wParam, lParam ); } return 0; } HWND InitAll() { WNDCLASSEX wcex; ZeroMemory( &wcex, sizeof( wcex ) ); wcex.cbSize = sizeof( WNDCLASSEX ); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = ( WNDPROC )WndProc; wcex.hInstance = _hInst; wcex.hCursor = LoadCursor( NULL, IDC_ARROW ); wcex.hbrBackground = ( HBRUSH )( COLOR_WINDOW + 1 ); wcex.lpszClassName = "_MY_PLASMA_";
RegisterClassEx( &wcex );
RECT rc = { 0, 0, BMP_SIZE, BMP_SIZE }; AdjustWindowRect( &rc, WS_SYSMENU | WS_CAPTION, FALSE ); int w = rc.right - rc.left, h = rc.bottom - rc.top; return CreateWindow( "_MY_PLASMA_", ".: Plasma -- PJorente :.", WS_SYSMENU, CW_USEDEFAULT, 0, w, h, NULL, NULL, _hInst, NULL ); } static wnd* _inst; HINSTANCE _hInst; HWND _hwnd; plasma _plasma;
}; wnd* wnd::_inst = 0; int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow ) {
wnd myWnd; return myWnd.Run( hInstance );
} </lang>
Common Lisp
plasma_demo.lisp: <lang lisp>(require :lispbuilder-sdl) (require :simple-rgb)
(defparameter *palette*
(let ((palette-aux (make-array 256 :element-type 'fixnum))) (dotimes (i 256) (let ((color_i (simple-rgb:hsv->rgb (simple-rgb:hsv (/ i 255.0) 1.0 1.0)))) (setf (aref palette-aux i) (loop :for component :across color_i :for i :from 0 :sum (ash component (* 8 i)))))) palette-aux) "palette")
(defun value->color (palette palette-shift index)
(aref palette (mod (+ index palette-shift) (length palette))))
(defun return-color-by-pos (x y &optional w h)
"returns a color index" (floor (/ (+ (+ 128.0 (* 128.0 (sin (/ x 16.0)))) (+ 128.0 (* 128.0 (sin (/ y 8.0)))) (+ 128.0 (* 128.0 (sin (/ (+ x y) 16.0)))) (+ 128.0 (* 128.0 (sin (/ (sqrt (+ (* x x) (* y y))) 8.0))))) 4.0)))
(defun return-color-by-pos-another (x y &optional w h)
"a different function that returns a color index" (floor (/ (+ (+ 128.0 (* 128.0 (sin (/ x 16.0)))) (+ 128.0 (* 128.0 (sin (/ y 32.0)))) (+ 128.0 (* 128.0 (sin (/ (sqrt (+ (expt (/ (- x w) 2.0) 2) (expt (/ (- y h) 2.0) 2))) 8.0)))) (+ 128.0 (* 128.0 (sin (/ (sqrt (+ (* x x) (* y y))) 8.0))))) 4.0)))
(defun plasma-render (surface palette-shift)
"render plasma" (let ((width (sdl:width surface)) (height (sdl:height surface))) (sdl-base::with-pixel (s (sdl:fp surface)) (dotimes (h height) (dotimes (w width) (sdl-base::write-pixel s w h (value->color *palette* palette-shift (funcall #'return-color-by-pos-another w h width height))))))) surface)
(defun demo/plasma ()
"main function: shows a window rendering a plasma efect" (sdl:with-init () (let ((win (sdl:window 320 240 :bpp 24 :resizable nil :title-caption "demo/plasma" :icon-caption "demo/plasma"))) (let ((palette-shift 0)) (sdl:update-display win) (sdl:with-events () (:idle (plasma-render win palette-shift) (sdl:update-display win) (incf palette-shift)) (:video-expose-event () (sdl:update-display win)) (:key-down-event (:key key) (when (or (sdl:key= key :sdl-key-escape) (sdl:key= key :sdl-key-q)) (sdl:push-quit-event))) (:quit-event () t))))))
(demo/plasma)</lang>
J
<lang j>require 'trig viewmat' plasma=: 3 :0
'w h'=. y X=. (i. % <:) w Y=. (i. % <:) h x1=. sin X*16 y1=. sin Y*32 xy1=. sin (Y+/X)*16 xy2=. sin (Y +&.*:/ X)*32 xy1+xy2+y1+/x1
)</lang>
<lang j> viewmat plasma 256 256</lang>
Java
<lang java>import java.awt.*; import java.awt.event.*; import java.awt.image.*; import static java.awt.image.BufferedImage.*; import static java.lang.Math.*; import javax.swing.*;
public class PlasmaEffect extends JPanel {
float[][] plasma; float hueShift = 0; BufferedImage img;
public PlasmaEffect() { Dimension dim = new Dimension(640, 640); setPreferredSize(dim); setBackground(Color.white);
img = new BufferedImage(dim.width, dim.height, TYPE_INT_RGB); plasma = createPlasma(dim.height, dim.width);
// animate about 24 fps and shift hue value with every frame new Timer(42, (ActionEvent e) -> { hueShift = (hueShift + 0.02f) % 1; repaint(); }).start(); }
float[][] createPlasma(int w, int h) { float[][] buffer = new float[h][w];
for (int y = 0; y < h; y++) for (int x = 0; x < w; x++) {
double value = sin(x / 16.0); value += sin(y / 8.0); value += sin((x + y) / 16.0); value += sin(sqrt(x * x + y * y) / 8.0); value += 4; // shift range from -4 .. 4 to 0 .. 8 value /= 8; // bring range down to 0 .. 1
// requires VM option -ea assert (value >= 0.0 && value <= 1.0) : "Hue value out of bounds";
buffer[y][x] = (float) value; } return buffer; }
void drawPlasma(Graphics2D g) { int h = plasma.length; int w = plasma[0].length; for (int y = 0; y < h; y++) for (int x = 0; x < w; x++) { float hue = hueShift + plasma[y][x] % 1; img.setRGB(x, y, Color.HSBtoRGB(hue, 1, 1)); } g.drawImage(img, 0, 0, null); }
@Override public void paintComponent(Graphics gg) { super.paintComponent(gg); Graphics2D g = (Graphics2D) gg; g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
drawPlasma(g); }
public static void main(String[] args) { SwingUtilities.invokeLater(() -> { JFrame f = new JFrame(); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setTitle("Plasma Effect"); f.setResizable(false); f.add(new PlasmaEffect(), BorderLayout.CENTER); f.pack(); f.setLocationRelativeTo(null); f.setVisible(true); }); }
}</lang>
Racket
Uses `return-color-by-pos` from #Lisp, because it was almost lift and shift
<lang racket>#lang racket
- from lisp (cos I could just lift the code)
(require images/flomap
2htdp/universe racket/flonum)
- copied from pythagoras-triangle#racket
(define (real-remainder x q) (- x (* (floor (/ x q)) q)))
(define (HSV->RGB H S V)
(define C (* V S)) ; chroma (define H′ (/ H 60)) (define X (* C (- 1 (abs (- (real-remainder H′ 2) 1))))) (define-values (R1 G1 B1) (cond [(< H′ 1) (values C X 0.)] [(< H′ 2) (values X C 0.)] [(< H′ 3) (values 0. C X)] [(< H′ 4) (values 0. X C)] [(< H′ 5) (values X 0. C)] [(< H′ 6) (values C 0. X)] [else (values 0. 0. 0.)])) (define m (- V C)) (values (+ R1 m) (+ G1 m) (+ B1 m)))
(define ((colour-component-by-pos ϕ) k x y)
(let ((rv (/ (+ (+ 1/2 (* 1/2 (sin (+ ϕ (/ x 16.0))))) (+ 1/2 (* 1/2 (sin (+ ϕ (/ y 8.0))))) (+ 1/2 (* 1/2 (sin (+ ϕ (/ (+ x y) 16.0))))) (+ 1/2 (* 1/2 (sin (+ ϕ (/ (sqrt (+ (sqr x) (sqr y))) 8.0)))))) 4.0))) rv))
(define ((plasma-flomap (ϕ 0)) w h)
(build-flomap 1 w h (colour-component-by-pos ϕ)))
(define ((plasma-image (ϕ 0)) w h)
(flomap->bitmap ((plasma-flomap ϕ) w h)))
(define ((colour-plasma plsm) t)
(let ((w (flomap-width plsm)) (h (flomap-height plsm))) (flomap->bitmap (build-flomap* 3 w h (λ (x y) (define-values (r g b) (HSV->RGB (real-remainder (+ (* t 5.) (* 360 (flomap-ref plsm 0 x y))) 360.) 1. 1.)) (flvector r g b))))))
- ((plasma-image) 200 200)
- ((plasma-image (/ pi 32)) 200 200)
(define plsm ((plasma-flomap) 300 300))
(animate (λ (t) ((colour-plasma plsm) t)))</lang>
Perl 6
<lang perl6>use Image::PNG::Portable;
my ($w, $h) = 400, 400; my $out = Image::PNG::Portable.new: :width($w), :height($h);
plasma($out);
$out.write: 'Plasma-perl6.png';
sub plasma ($png) {
for ^$w -> $x { for ^$h -> $y { my $hue = 4 + sin($x / 19) + sin($y / 9) + sin(($x + $y) / 25) + sin(sqrt($x² + $y²) / 8); $png.set: $x, $y, |hsv2rgb($hue/8, 1, 1); } }
}
sub hsv2rgb ( $h, $s, $v ){
my $c = $v * $s; my $x = $c * (1 - abs( (($h*6) % 2) - 1 ) ); my $m = $v - $c; my ($r, $g, $b) = do given $h { when 0..^1/6 { $c, $x, 0 } when 1/6..^1/3 { $x, $c, 0 } when 1/3..^1/2 { 0, $c, $x } when 1/2..^2/3 { 0, $x, $c } when 2/3..^5/6 { $x, 0, $c } when 5/6..1 { $c, 0, $x } } ( $r, $g, $b ) = map { (($_+$m) * 255).Int }, $r, $g, $b;
}</lang>
Sidef
<lang ruby>require('Imager')
class Plasma(width=400, height=400) {
has img = nil
method init { img = %s|Imager|.new(xsize => width, ysize => height) }
method generate { for y,x in (^height ~X ^width) { var hue = (4 + sin(x/19) + sin(y/9) + sin((x+y)/25) + sin(hypot(x, y)/8)) img.setpixel(x => x, y => y, color => Hash(hsv => [360 * hue / 8, 1, 1])) } }
method save_as(filename) { img.write(file => filename) }
}
var plasma = Plasma(256, 256) plasma.generate plasma.save_as('plasma.png')</lang>