Sortir pikselnya

35

Tugas Anda adalah membuat program yang akan, diberi gambar input, membuat gambar output dengan ukuran yang sama, di mana semua piksel disusun berdasarkan nilai hex.

Program Anda dapat:

  • Urutkan piksel dari kiri ke kanan lalu ke bawah atau pertama-tama mengurutkan dalam kolom dan kemudian ke kanan. Bagaimanapun, pixel kiri atas adalah yang terkecil, dan kanan bawah adalah yang terbesar.
  • Gunakan transparansi, tetapi ini tidak diperlukan.
  • Urutkan berdasarkan RGB, tetapi Anda dapat menggunakan CMY, atau format lain dengan setidaknya 3 nilai. Anda dapat memilih nilai untuk disortir. (HSV dapat memberikan beberapa gambar yang bagus)
  • Gunakan format gambar terkenal apa saja yang dapat dibuka oleh kebanyakan komputer.

Aturan:

  • Keluaran harus ditulis ke disk atau dapat dikirimkan ke file.
  • Input diberikan sebagai argumen commandline, dalam bentuk jalur relatif ke gambar, atau disalurkan dari commandline.
  • Ini kode golf, jadi kode terpendek dalam byte menang!
vrwim
sumber
2
Terkait
Martin Ender

Jawaban:

20

Pyth - 10 byte

Membaca gambar, menciutkan bitmap, mengurutkan, dan kemudian membagi bitmap lagi, lalu menulis.

.wclK'zSsK

Tidak bekerja online karena alasan yang jelas. Mengambil input sebagai jalur relatif ke file gambar dan menghasilkan o.png.

Output dari American Gothic:

Maltysen
sumber
3
Saya harap saya bukan satu-satunya orang yang memiliki kesan bahwa gambar bergerak ...
Quentin
Itu terlihat seperti kayu.
Joe Z.
19

JavaScript (ES6), 383 377 354 byte

f=s=>{d=document,i=new Image,i.src=s,i.onload=$=>{c=d.createElement`canvas`,x=c.getContext`2d`,c.width=w=i.width,c.height=h=i.height,x.drawImage(i,0,0),D=x.getImageData(0,0,w,h),t=D.data,t.set([].concat(...[...t].map((v,i,T)=>i%4?[,,,0]:T.slice(i,i+4)).sort((a,b)=>a.some((v,i)=>k=v-b[i])&&k)).slice(12*w*h)),x.putImageData(D,0,0),d.body.appendChild(c)}}

output sampel

Demo yang dapat dijalankan:

Cara kode ini digunakan adalah getImageDatauntuk mendapatkan array formulir

[R,G,B,A,
 R,G,B,A,
 R,G,B,A,
 ...]

Dan mapitu ke array formulir

[[R,G,B,A],[0,0,0,0],[0,0,0,0],[0,0,0,0],
 [R,G,B,A],[0,0,0,0],[0,0,0,0],[0,0,0,0],
 [R,G,B,A],[0,0,0,0],[0,0,0,0],[0,0,0,0],
 ...]

Sehingga nilai-nilai R dipetakan ke array dari set RGBA, dan nilai-nilai B, G, dan A berubah menjadi array nol nilai minimum. Ketika kita mengurutkan array ini, semua [0,0,0,0]array mengurutkan ke bawah dan array nilai riil mengurutkan secara normal di atas:

[[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],
 [0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],
 [0,0,0,0],..., [R,G,B,A],[R,G,B,A],[R,G,B,A],...]
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
               extract & flatten these sorted pixels

Kami membaca sekilas bagian atas array (untuk kehilangan nilai kosong yang kami buat), meratakannya dengan [].concat.apply, dan berakhir dengan array dari form pertama lagi, tapi kali ini, diurutkan.

Sedikit bermain golf dengan spasi putih dan komentar:

f=s=>{ 
  // first, load image, then do everything else onload
  i=new Image,
  i.src = s,
  i.onload=$=>{
    // set up the canvas
    d=document,
    c=d.createElement`canvas`,
    w=c.width=i.width,
    h=c.height=i.height,
    x=c.getContext`2d`,

    // draw image to canvas and capture pixel data
    x.drawImage(i,0,0),
    D=x.getImageData(0,0,w,h),
    t=D.data,

    // set pixel data to...
    t.set(
      // the flattened array...
      [].concat(...
        // that is a mapping of the pixel data...
        [...t].map(
          // that clumps RGBA families into subarrays
          // by replacing every fourth value with [R,G,B,A]
          // and all other values to [0,0,0,0]...
          (v,i,T)=>i%4?[,,,0]:T.slice(i,i+4)
        )
        // and is then sorted...
        .sort(
          // by reducing each array to a positive, negative, or zero
          // by comparing R,G,B,& A until the first nonzero difference
          (a,b)=>a.some((v,i)=>k=v-b[i])&&k
        )
      )
      // then eliminate the low-sorted empty [0,0,0,0] values we created,
      // leaving only the top fourth, with real values
      // (note that 3*4*w*h is the same as 3*t.length)
      .slice(3*4*w*h)
    ),

    // now that `t` is set, store `D` in canvas
    x.putImageData(D,0,0),

    // show canvas
    d.body.appendChild(c)
  }
}

Perhatikan bahwa sebagian besar peramban mungkin gagal menjalankan kode ini untuk gambar besar, karena memberikan sejumlah besar argumen [].concat. Ketika lingkungan browser tidak memungkinkan memori yang cukup untuk semua argumen, pendekatan alternatif adalah memetakan kembali nilai-nilai RGBA dari array keempat teratas kembali ke array, untuk skor total 361 byte :

f=s=>{d=document,i=new Image,i.src=s,i.onload=$=>{c=d.createElement`canvas`,x=c.getContext`2d`,c.width=w=i.width,c.height=h=i.height,x.drawImage(i,0,0),D=x.getImageData(0,0,w,h),t=D.data,t.set([...t].map((v,i,T)=>i%4?[,,,0]:T.slice(i,i+4)).sort((a,b)=>a.some((v,i)=>k=v-b[i])&&k).map((v,i,A)=>A[3*w*h+(i>>2)][i%4])),x.putImageData(D,0,0),d.body.appendChild(c)}}

Kami hanya mengganti [].concat(...{stuff}).slice(12*w*h)dengan {stuff}.map((v,i,A)=>A[3*w*h+(i>>2)][i%4]).)

apsillers
sumber
Bisakah Anda memasukkan contoh output?
Paŭlo Ebermann
@ PaŭloEbermann Selesai.
apsillers
@insertusernamehere Oh, sial, saya hanya menguji pada gambar kecil. concat.applyPanggilan saya memasok terlalu banyak argumen concatdan mesin JS menolaknya. D:Terima kasih! Saya akan memperbaikinya dan mencatat dua skor. (Dan saya senang saya bisa membantu!)
apsillers 3-15
@insertusernamehere Terima kasih atas informasi tentang batasan ukuran; Saya sekarang juga telah menerbitkan versi yang sedikit lebih panjang yang berfungsi pada gambar yang lebih besar.
apsillers
@apsillers Kerja bagus. Sayangnya, tidak dapat memperbaiki lagi. :)
insertusernamehere
14

Mathematica 86 83 72 byte

 f=Flatten;Image[f[Sort[f[s=ImageData@#1,1]]]~ArrayReshape~Dimensions@s]&

Dengan 14 byte yang disimpan berkat @Martin Buttner.


Contoh

Gambar itu sendiri adalah input. Atau, variabel yang berisi gambar dapat digunakan.

gothic

DavidC
sumber
Tantangan menentukan bahwa piksel diurutkan berdasarkan nilai hex, tetapi jika saya membacanya dengan benar, Anda menggunakan pengurutan standar Mathematica untuk daftar {R, G, B} atau {R, G, B, alpha}. Tidak jelas bagi saya bahwa ini setara.
Michael Stern
@MichaelStern Saya tidak tahu Mathematica, tetapi jika itu mengurutkan tuple dengan elemen seperti kebanyakan bahasa, mereka setara: Angka-angka seperti hex diurutkan berdasarkan setiap digit, dua di antaranya diwakili oleh masing-masing elemen dalam tuple.
Maltysen
@MichaelStern, Maltysen benar. Pengurutan RGB dan pengurutan Hex adalah sama: pengurutan menurut R, lalu G, maka nilai B bekerja seperti pengurutan nilai tempat di Hex.
DavidC
Oke, +1 dari saya.
Michael Stern
ImageDatadan ArrayReshapebisa menggunakan notasi infiks. Flattencukup lama untuk menyimpan beberapa byte dengan menetapkannya f. Dan apakah Anda benar-benar membutuhkan "Byte"? Bukankah standar hanya akan menskala nilai saluran [0,1]sedemikian rupa sehingga penyortiran dan rekonstruksi gambar masih akan berfungsi dengan baik?
Martin Ender 3-15
5

Javascript ES6, 334 byte

f=s=>{with((d=document).body.appendChild(c=d.createElement`canvas`).getContext`2d`)(i=new Image).src=s,drawImage(i,0,0,w=c.width=i.width,h=c.height=i.height),t=(g=getImageData(0,0,w,h)).data,t.set([...t].map(i=>(0+i.toString(16)).slice(-2)).join``.match(/.{8}/g).sort().join``.match(/../g).map(i=>parseInt(i,16))),putImageData(g,0,0)}

Tidak Disatukan:

f=s=>{                                   // create function that accepts image name
 with((d=document).body.appendChild(     // use "with" to exclude having to prepend "<context2d>." to drawImage, getImageData and putImageData
   c=d.createElement`canvas`).getContext`2d`) // create canvas to get pixels from and draw output to
  (i=new Image).src=s,                   // create image, define source filename
  drawImage(i,0,0,w=c.width=i.width,     // draw image to canvas
                  h=c.height=i.height),
  t=(g=getImageData(0,0,w,h)).data,      // get image data from canvas in form of Uint8Array
  t.set([...t]                           // convert image data from Uint8Array to standard array
   .map(i=>(0+i.toString(16)).slice(-2)) // convert R,G,B,A bytes to base16 strings with leading zeros
   .join``.match(/.{8}/g)                // convert array of [R,G,B,A,R,G,B,A,...] to [RGBA,RGBA,...]
   .sort()                               // sort pixel values
   .join``.match(/../g)                  // convert array of [RGBA,RGBA,...] to [R,G,B,A,R,G,B,A,...]
   .map(i=>parseInt(i,16))),             // convert hex strings back to integers, reassign to image data
  putImageData(g,0,0)                    // dump image data onto canvas
}
Dendrobium
sumber
@insertusernamehere Ini berfungsi pada Firefox terbaru. Seperti jawaban Anda, itu mengasumsikan ada tubuh untuk menambahkan kanvas dan bahwa sumber gambar berasal dari domain yang sama.
Dendrobium
+1 solusi yang sangat ramping. Ini juga memproses gambar hingga 1600 x 1900px.
masukkan nama pengguna di sini
2
Hari ini saya belajar yang appendChildmengembalikan argumennya. Sangat berguna! Anda mengilhami saya untuk mengurangi entri saya dari 377 ke 354, tapi saya tidak bisa mengalahkan entri Anda :). (Ketika saya menggunakan appendChildrantai dan withteknik Anda, saya bisa mencapai 347, tapi masih 13 pergi!) Kerja luar biasa!
apsillers
5

C (menggunakan SDL1.2), 333 322 315 byte

C kemungkinan besar bukan 'pisau paling tajam di rak' untuk jenis pekerjaan ini, toh saya ingin mencobanya. Tips untuk meningkatkan jawaban saya dipersilahkan. Program mendapatkan nama file gambar input sebagai argumen cli.

#include <SDL.h>
#include <SDL_image.h>
#define X SDL_Surface*
#define Y const void*
C(Y a,Y b){return*(Uint32*)a-*(Uint32*)b;}main(int o,char**a){X i=IMG_Load(a[1]);X s=SDL_SetVideoMode(i->w,i->h,32,0);i=SDL_ConvertSurface(i,s->format,0);qsort(i->pixels,i->w*i->h,4,C);SDL_BlitSurface(i,0,s,0);for(;;SDL_Flip(s));}

kompilasi dan jalankan: gcc -I/usr/include/SDL snippet.c -lSDL -lSDL_image && ./a.out

masukkan deskripsi gambar di sini

Saya biasanya tidak bermain golf di C, tapi saya baru saja menjawab tantangan ini kemarin dan saya hanya ingin terus bermain dengan mainan baru itu :)

terima kasih kepada @ pseudonym117 karena membantu saya menghemat 5 byte

pelaku diet
sumber
Dapat menghemat 1 byte dengan mengubah Anda whilepada akhirnya for(;;SDL_Flip(s));, dan saya yakin Anda dapat menghilangkan intmetode ini Cdan menyimpan 4 byte lagi.
pseudonym117
4

JavaScript (ES6), 452 480 484 487 511 byte

Wow, ini lebih lama dari yang diharapkan:

f=u=>{i=new Image;i.src=u;i.onload=_=>{c=(d=document).createElement`canvas`;c.width=w=i.width;c.height=h=i.height;x=c.getContext`2d`;x.drawImage(i,0,0,w,h);p=x.getImageData(0,0,w,h).data;t=[];f=[];for(j=0;j<p.length;++j)t.push([p[j],p[++j],p[++j],p[++j]]);t.sort((a,b)=>a[0]>b[0]||a[0]==b[0]&&a[1]>b[1]||a[0]==b[0]&&a[1]==b[1]&&a[2]>b[2]).map(u=>f.push.apply(f,u));x.putImageData(new ImageData(new Uint8ClampedArray(f),w,h),0,0);d.body.appendChild(c)}}

Fungsi mengambil URL sebagai inputnya f('test.jpg');dan menarik hasilnya menjadi canvaselemen yang ditambahkan ke body.

Perhatikan bahwa sumber harus pada domain yang sama atau skrip akan berhenti dengan masalah keamanan.


Keterbatasan

Saya sudah mengujinya di Firefox 42 pada OS X (10.10) pada mesin dengan 2,5 GHz i7 dan 16 GB RAM. Ukuran gambar maksimum yang dapat saya proses tanpa Firefox yang meminta untuk melanjutkan eksekusi skrip adalah 1600 x 1932 px .


Tidak disatukan

f = u => {
    i = new Image;
    i.src = u;
    i.onload = _ => {
        c = (d = document).createElement`canvas`;
        c.width = w = i.width;
        c.height = h = i.height;

        x = c.getContext`2d`;
        x.drawImage(i, 0, 0, w, h);

        p = x.getImageData(0, 0, w, h).data;

        t = [];
        f = [];

        for (j = 0; j < p.length;++j)
            t.push([p[j], p[++j], p[++j], p[++j]]);

        t.sort( (a,b) => a[0] > b[0] || a[0] == b[0] && a[1] > b[1] || a[0] == b[0] && a[1] == b[1] && a[2] > b[2] )
         .map(u => f.push.apply(f,u));

        x.putImageData( new ImageData( new Uint8ClampedArray(f), w, h), 0, 0);
        d.body.appendChild(c)
    }
}

Keluaran

Untuk perbandingan yang lebih baik, saya juga mengambil " American Gothic " sebagai contoh sumber:

masukkan deskripsi gambar di sini


Suntingan

  • Disimpan 24 byte dengan menggunakan for (a in b)alih-alih for(;;). Berkat ar34z
  • Disimpan 3 byte dengan menyimpan documentdalam variabel.
  • Disimpan 4 byte dengan menjatuhkan beberapa di antaranya ().
  • Disimpan 10 byte dengan menggunakan string template yang ditandai , menghilangkan ()penciptaan objek on dan menghapus sepasang redundan lainnya (). Terima kasih kepada apsillers .
  • Disimpan 14 byte oleh refactoring besar-besaran dari kode yang meratakan array warna setelah pengurutan. Terima kasih banyak untuk apsillers dan Ypnypn karena saling meremehkan satu sama lain.
  • Disimpan 1 byte dengan refactoring for-loop yang mendapatkan warna dari setiap piksel.
masukkan nama pengguna di sini
sumber
1
Anda dapat meminimalkan penggunaan for-loop for(k in t)yang akan menghemat beberapa byte lagi :)
ar34z
1
Kerja bagus! Beberapa perbaikan: kehilangan ()in new Image(); gunakan string templat yang ditandai untuk argumen string Anda ( createElement`canvas`, getContext`2d`), jangan gunakan tanda kurung untuk parameter fungsi panah tunggal (lakukan saja f=u=>{...}; parens hanya untuk fungsi panah multi-parameter atau nol-parameter). Anda juga mungkin memiliki satu atau dua forloop pernyataan tunggal yang memiliki tanda kurung, yang tidak perlu.
apsillers
Oh, sebenarnya, untuk funcs panah argumen-nol, gunakan argumen dummy karakter tunggal alih-alih parens kosong dua-char. ( i.onload=$=>...Alih-alih i.onload=()=>...)
apsillers
Saya pikir for(l in u)f.push(u[l]);bisa menjadifor(z of u)f.push(z);
Ypnypn
@Ypnypn Bahkan bisa lebih pendek dari itu :). - for(u of t)for(z of u)f.push(z)Cukup pendek, tapi bisa dipersingkat lebih lanjut t.map(u=>u.map(z=>f.push(z))). Dalam banyak kasus, menggunakan .mapatau .somedengan fungsi panah akan lebih pendek daripada menggunakan forloop. Jika Anda ingin pergi benar-benar gila, Anda dapat menyimpan lebih banyak di sini dengan t.map(u=>f.push.apply(f,u));yang mengatakan "Untuk setiap array yang udi t, pasokan usebagai daftar argumen untuk f.pushmelalui apply(karena pushdapat menerima jumlah tak terbatas argumen dan mendorong semua dari mereka dalam rangka).
apsillers
4

Utilitas Bash + GNU, 80

s()(sed 1d $1|cut -d\  -f$2)
sed 1q $1
s $1 2-|sort -t_ -k1.16|paste <(s $1 1) -

Ini mengasumsikan format input / output dalam format .txt enumerasi piksel ImageMagick. Input dilewatkan sebagai nama file dan output pergi ke STDOUT.


Jika hal di atas tidak dianggap sebagai format gambar yang terkenal, maka kita dapat menambahkan konversi yang diperlukan:

Bash + Utilitas GNU + ImageMagick, 108

s()(sed 1d t|cut -d\  -f$1)
convert $1 txt:t
(sed 1q t
s 2-|sort -t_ -k1.16|paste <(s 1) -)|convert txt:- $2

Input dan output ditetapkan sebagai nama file. ImageMagick menentukan format file apa yang akan digunakan oleh ekstensi file yang disahkan, sehingga kami dapat menggunakan yang umum:

$ ./sortpixels.sh 398px-Grant_Wood_-_American_Gothic_-_Google_Art_Project.jpg o.png
$ 

O.png yang dihasilkan terlihat seperti:

masukkan deskripsi gambar di sini

Trauma Digital
sumber
3

Python 2, 128 byte

from PIL import*
a=Image.open('a')
b=a.load()
c,d=a.size
a.putdata(sorted(b[e,f]for f in range(d)for e in range(c)))
a.save('b')

Asalkan gambar adalah file bernama atanpa ekstensi, output akan menjadi file bernama btanpa ekstensi.

American Gothic American Gothic (diurutkan)

Zach Gates
sumber
Saya belum memeriksa, tetapi Anda seharusnya bisa melakukan sesuatu seperti itu a.putdata(sorted(b[f/c,f%d]for f in range(d*c)))(saya baru saja bangun, jadi saya mungkin telah mencampuradukkan variabel).
Kade
Ketika Anda menulisnya, itu tidak berhasil (indeks di luar jangkauan), tapi saya belum mencoba beralih variabel apa pun (saya tidak punya banyak waktu saat ini). @ Shebang
Zach Gates
3

Java, 316 byte

import javax.imageio.*;class C{public static void main(String[]a)throws Exception{java.awt.image.BufferedImage i=ImageIO.read(new java.io.File(a[0]));int w=i.getWidth(),h=i.getHeight(),v[]=i.getRGB(0,0,w,h,null,0,w);java.util.Arrays.sort(v);i.setRGB(0,0,w,h,v,0,w);ImageIO.write(i,"png",new java.io.File("a.png"));}}

Tempatkan nilai hexadecimal dari warna pixel dalam array. Array diurutkan, dan warnanya dipetakan kembali ke piksel dalam gambar. Nama gambar yang dihasilkan adalah a.png.

input tulip kuning output tulip kuning
Masukan Amerika Gothic masukkan deskripsi gambar di sini

TNT
sumber
3

SmileBASIC, 39 35 byte

Dengan asumsi gambar dimuat ke halaman grafis 512 * 512:

DIM A[0]GSAVE A,0SORT A
GLOAD A,0,1

Dijelaskan:

DIM IMG[0] 'create array
GSAVE IMG,0 'save graphics to array
SORT IMG 'sort
GLOAD IMG,0,1 'load graphics from array

Sesederhana itu! Sayangnya kita harus menggunakan bilangan bulat, yang menambahkan 4 byte ke ukuran program karena sufiks tipe.

12Me21
sumber
Saya tidak begitu yakin mengapa int diperlukan. Sepertinya menggunakan pelampung sebenarnya mendapatkan hasil yang benar. Menjalankan kode ini menggunakan ints on SYS/DEFSP.GRPmenempatkan a FF000000di kiri atas dan 00101010di kanan bawah, yang merupakan kebalikan dari pertanyaan. Menggunakan pelampung menempatkan 00000000di kiri atas dan FFF8F8F8di kanan bawah, yang benar. (Tentu saja, ini memperlakukan warna hex sebagai unsigned / channel yang lebih tinggi lebih besar, yang mungkin benar.)
snail_
Saya pikir itu valid, karena pertanyaannya tidak menentukan urutan khusus untuk mengurutkan berdasarkan (dan karena nilai-nilai yang ditandatangani, 0xFF000000lebih kecil dari 0x00101010), tetapi bagaimanapun, saya tidak benar-benar yakin mengapa saya menggunakan bilangan bulat di sini ... Saya pikir di waktu saya tidak mengerti bagaimana GLOAD menggunakan nilai yang tidak ditandatangani ketika Anda menggunakan array float, dan hanya menganggap itu tidak berfungsi.
12Me21
2

Java, 424 417 404 byte

Ya, ini bukan bahasa yang Anda inginkan untuk bermain golf ...

import java.awt.image.*;import java.io.*;import javax.imageio.*;class F{public static void main(String[]x)throws Exception{BufferedImage i,o;i=ImageIO.read(new File(x[0]));o=new BufferedImage(i.getWidth(),i.getHeight(),BufferedImage.TYPE_INT_RGB);o.setData(i.getRaster());int[]p=((DataBufferInt)o.getRaster().getDataBuffer()).getData();java.util.Arrays.sort(p);ImageIO.write(o,"png",new File("o.png"));}}
Peter Lenkefi
sumber
2

C #, 497 byte

Posting pertama kali, golf pertama. Jelas bukan yang terbaik untuk bermain golf

Tidak benar-benar menghormati perpipaan. Mengambil jalur gambar sebagai input dan mengeluarkannya dengan huruf "o" yang sesuai dengan namanya.

Bekerja lebih baik dengan bitmap, hasil odds dengan orang lain

using System.Linq;using System.Drawing;using System.Runtime.InteropServices;class Program{static void Main(string[]args){using(var im=(Bitmap)Image.FromFile(args[0])){int h=im.Height;int w=im.Width;var b=im.LockBits(new Rectangle(0,0,w,h),System.Drawing.Imaging.ImageLockMode.ReadWrite,System.Drawing.Imaging.PixelFormat.Format32bppRgb);var p=new int[h*w];Marshal.Copy(b.Scan0,p,0,h*w);var q=p.ToList();q.Sort();p=q.ToArray();Marshal.Copy(p,0,b.Scan0,h*w);im.UnlockBits(b);im.Save("o"+args[0]);}}}
Cylianna
sumber
1

Haskell, 195 byte

import Data.List
import Graphics.GD
f p=do 
 a<-loadPngFile p;(x,y)<-imageSize a;let l=[(i,j)|j<-[0..y],i<-[0..x]]
 mapM(flip getPixel a)l>>=mapM(\(d,c)->setPixel d c a).zip l.sort;savePngFile"o"a

Ini menggunakan GDperpustakaan. Penggunaan f <filename>. File input harus dalam pngformat. File keluaran dinamai o.

Cara kerjanya: langsung, yaitu membaca gambar, berjalan di atas semua koordinat dan mendapatkan piksel, mengurutkan piksel, berjalan di atas koordinat lagi, tetapi kali ini atur piksel sesuai urutan yang ditampilkan dalam daftar yang diurutkan, tulis file ke disk.

masukkan deskripsi gambar di sini

nimi
sumber