Pikselleri sırala


35

Göreviniz, bir giriş görüntüsü verildiğinde, tüm piksellerin onaltılık değere göre sıralandığı aynı boyutta bir çıkış görüntüsü oluşturacak bir program oluşturmaktır.

Programınız şunları yapabilir:

  • Pikselleri soldan sağa ve sonra aşağı ya da önce sütunlar halinde sıralayıp sonra sağa sıralayın. Her durumda, sol üst piksel en küçük ve sağ alt en büyüğüdür.
  • Saydamlığı kullanın, ancak bu gerekli değildir.
  • RGB'ye göre sıralayın, ancak CMY'yi veya en az 3 değeri olan başka bir formatı kullanabilirsiniz. Hangi değerlerin sıralanacağını seçebilirsiniz. (HSV güzel görüntüler verebilir)
  • Çoğu bilgisayarın açabileceği bilinen herhangi bir görüntü formatını kullanın.

Kurallar:

  • Çıktı diske yazılmalıdır veya bir dosyaya aktarılabilir olmalıdır.
  • Girdi, görüntünün göreceli yolu biçiminde bir komut satırı argümanı olarak verilir veya komut satırından aktarılır.
  • Bu kod golf, bayt cinsinden en kısa kod kazanır!

Yanıtlar:


20

Pyth - 10 bayt

Görüntüyü okur, bitmap'i daraltır, sıralar ve sonra tekrar bitmap'i böler, sonra yazar.

.wclK'zSsK

Belirgin nedenlerden dolayı çevrimiçi çalışmıyor. Girdiyi görüntü dosyasına göreceli yol olarak alır ve çıktılar o.png.

Amerikan Gotikten Çıktı:


3
Umarım, resmin hareket ettiği izlenimini alan tek kişi ben değilimdir ...
Quentin

Tahta gibi görünüyor.
Joe Z.

19

JavaScript (ES6), 383 377 354 bayt

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

örnek çıktı

Çalıştırılabilir demo:

Bu kod nasıl çalışır getImageDataformun bir dizisini almak için kullanmaktır

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

Ve mapbu bir form dizisine

[[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],
 ...]

Böylece R değerleri, RGBA kümesinin dizilerine eşlenir ve B, G ve A değerleri minimum değer sıfır dizilerine dönüşür. Bu diziyi sıraladığımızda, tüm [0,0,0,0]diziler en alt seviyeye, gerçek değer dizileri ise en üstte sıralanır:

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

Dizinin ilk dördüncü kısmını gözden geçiririz (yarattığımız boş değerleri kaybetmek için), onu düzleştirir [].concat.applyve ilk formun bir dizisini tekrar buluruz , ancak bu sefer sıralanır.

Boşluk ve yorumlarla hafifçe çözüldü:

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

Tarayıcıların çoğu bu görüntüyü büyük resimler için çalıştırmakta başarısız olabilir, çünkü çok sayıda argüman iletir [].concat. Tarayıcı ortamı tüm argümanlar için yeterli belleğe izin vermediğinde, alternatif bir yaklaşım, toplam 361 baytlık bir skor için, RGBA değerlerini ilk dördüncü dizilerden tekrar diziye yeniden eşlemektir :

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

Biz sadece yerine [].concat(...{stuff}).slice(12*w*h)sahip {stuff}.map((v,i,A)=>A[3*w*h+(i>>2)][i%4]).)


Örnek bir çıktı ekler misiniz?
Paŭlo Ebermann

@ PaŭloEbermann Tamamlandı.
apsillers

@insertusernamehere Ah, dang, sadece küçük resimler üzerinde test ettim. Benim concat.applyçağrı çok fazla argüman temin edilir concatve JS motoru bunu reddediyor. D:Teşekkürler! Bunu düzeltip iki puanı not alacağım. (Ve yardım edebildiğime sevindim!)
apsillers

@insertusernamehere Beden sınırlaması konusundaki yardım için teşekkürler; Ayrıca daha büyük resimler üzerinde çalışan biraz daha uzun bir sürüm yayınladım.
apsillers

@ apsillers Güzel iş. Maalesef tekrar oylayamıyorum. :)
insertusernamehere

14

Mathematica 86 83 72 bayt

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

Martin Buttner sayesinde 14 byte kaydedildi.


Örnek

Resmin kendisi girilmiştir. Alternatif olarak, görüntüyü içeren bir değişken kullanılabilir.

Gotik


Zorluk, piksellerin hex değerine göre sıralandığını belirtir, ancak bunu doğru okursam, Mathematica'nın varsayılan sıralamasını {R, G, B} veya {R, G, B, alpha} listeleri için kullanırsınız. Bunların eşdeğer olduğu bana açık değil.
Michael Stern

@MichaelStern Mathematica'yı tanımıyorum, ancak birçok dilde olduğu gibi elementleri elementlere göre sıralarsa, bunlar eşdeğerdir: hex gibi sayılar, her ikisi de tuple içindeki her bir basamağa göre temsil edilen her hane tarafından sıralanır.
Maltysen

@MichaelStern, Maltysen doğru. RGB sıralama ve Hex sıralama eşdeğerdir: R, sonra G, ardından B'ye göre sıralama B'deki değer, Hex'taki yer değeri sıralama gibi çalışır.
DavidC

Tamam, benden +1.
Michael Stern

ImageDatave ArrayReshapeinfix notasyonu kullanabilirler. Flattenatayarak birkaç bayttan tasarruf edecek kadar uzun f. Ve gerçekten ihtiyacın var "Byte"mı? Varsayılan değer, kanal değerlerini [0,1]sıralama ve görüntü yapılandırma işlemlerinin hala iyi çalışacağı şekilde ölçeklendirmez mi?
Martin Ender

5

Javascript ES6, 334 bayt

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

Ungolfed:

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
}

@ insertusernamehere En son Firefox'ta çalışır. Cevabınız gibi, tuvali ekleyecek bir gövde olduğunu ve kaynak görüntünün aynı etki alanında olduğunu varsayar.
Dendrobium

+1 Çok şık bir çözüm. Ayrıca, 1600 x 1900 piksele kadar olan görüntüleri de işler.
insertusernamehere

2
Bugün appendChildbunun argümanını döndürdüğünü öğrendim . Çok kullanışlı! Girişimi 377'den 354'e düşürmem için bana ilham verdin, ama ben seninkini yenemem :). ( appendChildZincirleme withtekniğini ve tekniğini kullandığımda, onu 347'ye , ancak hala 13 uzağa indirebilirim!) Mükemmel iş!
apsillers

5

C (SDL1.2 kullanılarak), 333 322 315 bayt

C muhtemelen bu tür bir iş için 'raftaki en keskin bıçak' değildir, yine de denemek istedim. Cevabımı geliştirmek için ipuçları açıktır. Program, giriş görüntüsü dosyasının adını bir cli argümanı olarak alır.

#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));}

derleyin ve çalıştırın: gcc -I/usr/include/SDL snippet.c -lSDL -lSDL_image && ./a.out

görüntü tanımını buraya girin

Genelde C'de golf oynamam , ancak dün bu zorluğa cevap verdim ve o yeni oyuncakla oynamaya devam etmek istedim :)

5 bayttan tasarruf etmeme yardımcı olduğu için @ pseudonym117 sayesinde


whileSonunda for(;;SDL_Flip(s));ayarlarınızı değiştirerek 1 byte tasarruf edebilir intve yöntemi ihmal edip C4 tane daha tasarruf edebileceğinizi düşünüyorum .
takma

4

JavaScript (ES6), 452 480 484 487 511 bayt

Vay, bu beklenenden daha uzun sürdü:

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

İşlev bir URL'yi girdi olarak alır f('test.jpg');ve sonucu, kendisine canvaseklenmiş olan bir -element içine çeker body.

Kaynağın aynı alanda olması gerektiğini ya da komut dosyasının bir güvenlik sorunuyla durduğunu unutmayın.


Sınırlamalar

Bunu 2.5 GHz i7 ve 16 GB RAM'e sahip bir makinede OS X'teki Firefox 42'de (10.10) test ettim. Firefox'un betiğin yürütülmesine devam etmesini istemeden işleme koyabildiğim maksimum resim boyutu 1600 x 1932 piksel idi .


Ungolfed

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

Çıktı

Daha iyi bir karşılaştırma için örnek kaynak olarak " Amerikan Gotikini " de kullandım:

görüntü tanımını buraya girin


Düzenlemeler

  • Kaydedilen 24 bayt kullanarak for (a in b)yerine for(;;). Ar34z sayesinde
  • Bir değişkende saklayarak 3 bayt kaydedildidocument .
  • Bunlardan bazılarını bırakarak 4 bayt kurtarıldı() .
  • Etiketli şablon dizeleri kullanarak 10 bayt kaydedildi , nesne oluşturma işlemini iptal etti ve başka bir fazlalık çiftini kaldırarak . Apsisçilere teşekkürler .()()
  • Sıralamadan sonra renk dizisini düzleyen kodun büyük ölçüde yeniden düzenlenmesiyle 14 bayt kurtarıldı . Apsillers ve Ypnypn için birbirlerini kesmek için çok teşekkür ederiz .
  • Kaydedilen 1 byte üstlenmeden tarafından forher pikselin renklerini alır -loop.

1
Birkaç döngüyü for(k in t)daha tasarruf edecek olan for-döngülerini en aza indirebilirsiniz. :)
ar34z

1
İyi iş! Bazı iyileştirmeler: kaybetmek ()de new Image(); string argümanlarınız için etiketli şablon dizeleri kullanın ( createElement`canvas`, getContext`2d`), tek ok işlev parametreleri için parantez kullanmayın (sadece f=u=>{...}; parens sadece çok parametreli veya sıfır parametreli ok fonksiyonları içindir). Ayrıca for, gerekli olmayan, köşeli parantezlere sahip bir veya iki tane tek deyim döngüsüne sahip olabilirsiniz.
apsillers

Oh, aslında, sıfır-argüman ok funcs için, iki-karakterli boş parens yerine, tek-karakterli bir argüman kullanın. ( i.onload=$=>...yerine i.onload=()=>...)
apsillers

Ben for(l in u)f.push(u[l]);olabilir olabilirfor(z of u)f.push(z);
Ypnypn

@Ypnypn Olabilir da kısa bundan daha :). - for(u of t)for(z of u)f.push(z)oldukça kısa ve çok kısa bir süre için kısaltılabilir t.map(u=>u.map(z=>f.push(z))). Bir çok durumda, ok işlevini kullanmak .mapveya .somebir ok işleviyle fordöngü kullanmaktan daha kısa olacaktır . Eğer gerçekten çıldırmak istiyorsanız, burada t.map(u=>f.push.apply(f,u));“Her dizi uiçin t, üzerinden uyapılan bir argümanlar listesi sağlayın (çünkü sınırsız sayıda argüman kabul edip hepsini sıraya sokar) diyen daha fazla tasarruf edebilirsiniz.f.pushapplypush
apsillers

4

Bash + GNU yardımcı programları, 80

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

Bu giriş / çıkış formatının ImageMagick piksel numaralandırma .txt formatında olduğunu varsayar. Giriş bir dosya adı olarak iletilir ve çıkış STDOUT'a gider.


Yukarıdakiler iyi bilinen bir resim formatı olarak kabul edilmezse, gerekli dönüşümleri ekleyebiliriz:

Bash + GNU yardımcı programları + 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

Giriş ve çıkış dosya isimleri olarak belirtilir. ImageMagick, aktarılan dosya uzantıları tarafından hangi dosya formatlarının kullanılacağını belirler, böylece ortak olanları kullanabiliriz:

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

Sonuçtaki o.png şöyle görünür:

görüntü tanımını buraya girin


3

Python 2, 128 bayt

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

Resmin auzantısı olmayan bir dosya olması koşuluyla, çıktı, buzantısı olmayan bir dosya olacaktır .

Gotik amerikan Amerikan Gotik (sıralı)


Kontrol etmedim, ama a.putdata(sorted(b[f/c,f%d]for f in range(d*c)))bunun yerine satırları boyunca bir şeyler yapabilmelisiniz (sadece uyandım, bu yüzden değişkenleri karıştırdım).
Kade

Siz yazdığınız gibi, çalışmadı (endeks aralık dışı), ancak herhangi bir değişkeni değiştirmeyi denemedim (şu anda çok fazla zamanım yok). @Shebang
Zach Gates,

3

Java, 316 bayt

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"));}}

Piksel renklerinin onaltılık değerlerini bir diziye yerleştirir. Dizi sıralanır ve renkler görüntüdeki piksellere yeniden eşlenir. Ortaya çıkan görüntünün adı a.png.

sarı laleler girişi sarı laleler çıktı
Amerikan Gotik girişi görüntü tanımını buraya girin


3

SmileBASIC, 39 35 bayt

Görüntünün 512 * 512 grafik sayfasına yüklendiğini varsayarsak:

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

Açıklaması:

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

Bu kadar basit! Ne yazık ki tür sonekleri nedeniyle program boyutuna 4 bayt ekleyen tamsayıları kullanmamız gerekiyor.


Neden giriş yapılması gerektiğinden emin değilim. Şamandıraları kullanmak aslında sonuçları doğrulamış gibi görünüyor. Üzerinde int'leri kullanarak bu kodu Koşu SYS/DEFSP.GRPkoyar bir FF000000üst sol ve bir 00101010sorunun belirgin tersidir sağ alt içinde. Şamandıraların kullanılması 00000000sol üstte ve FFF8F8F8sağ alt kısımda yer alır, bu doğru. (Tabii ki, bu onaltılık renkleri imzasız / daha yüksek kanal olarak kabul eder, bu muhtemelen doğru olur.)
salyangoz_

Bunun geçerli olduğunu düşünüyorum, çünkü soru sıralamak için belirli bir düzen belirtmediği için (ve değerler imzalandığından beri 0xFF000000daha küçük 0x00101010) ama yine de, neden burada tamsayıları kullandığımı tam olarak bilmiyorum ... float dizisini kullandığınızda GLOAD'ın işaretsiz değerleri nasıl kullandığını anlamadığım ve sadece çalışmadığını varsaydığım zaman.
12Me21

2

Java, 424 417 404 bayt

Şey, bu golf oynamak istediğin bir dil değil ...

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"));}}

2

C #, 497 bayt

İlk sefer sonrası, ilk golf. Açıkçası golf oynamak için en iyisi değil

Boruya saygılı değil. Görüntü yolunu girdi olarak alır ve adı "ön" harfiyle yazar.

Bitmaplerle daha iyi çalışır, başkalarıyla sonuçlanır

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]);}}}

1

Haskell, 195 bayt

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

Bu GDkütüphaneyi kullanır . Kullanım f <filename>. Giriş dosyası pngformatta olmalıdır . Çıktı dosyası adlandırılmıştır o.

Nasıl çalışır: basit, yani resmi okuyun, tüm koordinatların üzerinden yürüyün ve pikselleri alın, pikselleri sıralayın, koordinatların üzerinde tekrar yürüyün, ancak bu kez pikselleri sıralanan listede göründükleri sıraya göre ayarlayın, dosyayı yazın. disk.

görüntü tanımını buraya girin

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.