Plasma effect

From Rosetta Code
Revision as of 16:45, 23 September 2016 by rosettacode>Fwend (no longer draft)
Task
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>

  1. include <windows.h>
  2. include <math.h>
  3. 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

Library: simple-rgb

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

Works with: Java version 8

<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>

Perl 6

Works with: Rakudo version 2016.03

<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

Translation of: Perl 6

<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>