Sortuj piksele


35

Twoim zadaniem jest stworzenie programu, który na podstawie obrazu wejściowego utworzy obraz wyjściowy o tym samym rozmiarze, w którym wszystkie piksele są uporządkowane według wartości szesnastkowej.

Twój program może:

  • Sortuj piksele od lewej do prawej, a następnie w dół lub najpierw sortuj w dół w kolumnach, a następnie w prawo. W każdym razie lewy górny piksel jest najmniejszy, a prawy dolny - największy.
  • Użyj przezroczystości, ale nie jest to wymagane.
  • Sortuj według RGB, ale możesz użyć CMY lub dowolnego innego formatu z co najmniej 3 wartościami. Możesz wybrać wartości według których chcesz sortować. (HSV może dać ładne zdjęcia)
  • Użyj dowolnego znanego formatu obrazu, który można otworzyć na większości komputerów.

Zasady:

  • Dane wyjściowe muszą być zapisane na dysku lub nadawać się do potoku do pliku.
  • Dane wejściowe są podawane jako argument wiersza polecenia, w postaci względnej ścieżki do obrazu lub przesyłane strumieniowo z wiersza polecenia.
  • To jest kod golfowy, więc wygrywa najkrótszy kod w bajtach!

Odpowiedzi:


20

Pyth - 10 bajtów

Odczytuje obraz, zwija bitmapę, sortuje, a następnie ponownie dzieli bitmapę, a następnie zapisuje.

.wclK'zSsK

Nie działa online z oczywistych powodów. Pobiera dane wejściowe jako względną ścieżkę do pliku obrazu i dane wyjściowe do o.png.

Wyjście z American Gothic:


3
Mam nadzieję, że nie tylko mam wrażenie, że obraz się porusza ...
Quentin

Wygląda jak drewno.
Joe Z.

19

JavaScript (ES6), 383 377 354 bajtów

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

sample output

Uruchomienie demo:

Jak działa ten kod, należy użyć, getImageDataaby uzyskać tablicę formularza

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

I mapto do tablicy formy

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

Tak więc wartości R są mapowane na tablice zestawu RGBA, a wartości B, G i A zamieniają się w tablice zerowej wartości minimalnej. Kiedy sortujemy tę tablicę, wszystkie [0,0,0,0]tablice są sortowane na dole, a tablice wartości rzeczywistych są sortowane normalnie na górze:

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

Przeczesujemy górną czwartą tablicy (aby utracić puste wartości, które stworzyliśmy), spłaszczamy ją [].concat.applyi kończymy tablicą pierwszej formy, ale tym razem jest posortowana.

Nieco golfa z białymi spacjami i komentarzami:

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

Zauważ, że większość przeglądarek może nie uruchomić tego kodu dla dużych obrazów, ponieważ przekazuje ogromną liczbę argumentów [].concat. Gdy środowisko przeglądarki nie pozwala na wystarczającą ilość pamięci dla wszystkich argumentów, alternatywnym podejściem jest ponowne mapowanie wartości RGBA z górnych czwartych tablic z powrotem na tablicę, co daje łączny wynik 361 bajtów :

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

Po prostu zastąpić [].concat(...{stuff}).slice(12*w*h)z {stuff}.map((v,i,A)=>A[3*w*h+(i>>2)][i%4])).


Czy możesz podać przykładowy wynik?
Paŭlo Ebermann

@ PaŭloEbermann Gotowe.
apsillers,

@insertusernamehere Oh, cholera, testowałem tylko na małych obrazach. Moje concat.applywezwanie dostarcza zbyt wielu argumentów, concata silnik JS je odrzuca. D:Dzięki! Naprawię to i zanotuję dwa wyniki. (I cieszę się, że mogłem pomóc!)
apsillers

@insertusernamehere Dziękujemy za zgłoszenie się na temat ograniczenia rozmiaru; Opublikowałem też nieco dłuższą wersję, która działa na większych obrazach.
apsillers,

@apsillers Dobra robota. Niestety nie mogę ponownie głosować. :)
inserttusernamehere

14

Mathematica 86 83 72 bajty

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

Z 14 bajtami zaoszczędzonymi dzięki @Martin Buttner.


Przykład

Sam obraz jest wprowadzany. Alternatywnie można zastosować zmienną zawierającą obraz.

gothic


Wyzwanie określa, że ​​piksele mają być sortowane według wartości szesnastkowej, ale jeśli poprawnie to odczytam, użyjesz domyślnego sortowania Mathematica dla list {R, G, B} lub {R, G, B, alpha}. Nie jest dla mnie jasne, czy są one równoważne.
Michael Stern,

@MichaelStern Nie znam Matematyki, ale jeśli sortuje krotki elementarnie jak większość języków, są one równoważne: Liczby takie jak szesnastkowe są sortowane według każdej cyfry, z których dwie są reprezentowane przez każdy element w krotce.
Maltysen

@MichaelStern, Maltysen ma rację. Sortowanie RGB i sortowanie szesnastkowe są równoważne: sortowanie według R, następnie G, a następnie wartość B działa tak samo, jak sortowanie według wartości miejsca w Hex.
DavidC

OK, +1 ode mnie.
Michael Stern,

ImageDatai ArrayReshapemoże użyć notacji infix. Flattenjest wystarczająco długi, aby zapisać kilka bajtów, przypisując go do f. A czy naprawdę potrzebujesz "Byte"? Czy domyślny nie skalowałby wartości kanału [0,1]tak, aby sortowanie i rekonstrukcja obrazu nadal działałyby dobrze?
Martin Ender

5

JavaScript ES6, 334 bajty

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

Nie golfowany:

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 Działa w najnowszym Firefoksie. Podobnie jak twoja odpowiedź, zakłada się, że istnieje ciało, do którego można dołączyć płótno i że obraz źródłowy pochodzi z tej samej domeny.
Dendrobium

+1 Bardzo eleganckie rozwiązanie. Przetwarza również obrazy o rozdzielczości do 1600 x 1900 pikseli.
inserttusernamehere

2
Dzisiaj dowiedziałem się, że appendChildzwraca swój argument. Bardzo przydatne! Zainspirowałeś mnie do zmniejszenia mojego wpisu z 377 do 354, ale nie mogę pokonać twojego :). (Kiedy używam twojego appendChildłańcucha i withtechniki, mogę sprowadzić go do 347, ale wciąż 13 daleko!) Świetna robota!
apsillery

5

C (przy użyciu SDL 1.2), 333 322 315 bajtów

C najprawdopodobniej nie jest „najostrzejszym nożem na półce” do tego rodzaju pracy, i tak chciałem spróbować. Wskazówki dotyczące poprawy mojej odpowiedzi są mile widziane. Program pobiera nazwę pliku obrazu wejściowego jako argument 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));}

skompiluj i uruchom: gcc -I/usr/include/SDL snippet.c -lSDL -lSDL_image && ./a.out

wprowadź opis zdjęcia tutaj

Zwykle nie gram w golfa w C, ale wczoraj odpowiedziałem na to wyzwanie i chciałem po prostu grać z tą nową zabawką :)

dzięki @ pseudonym117 za pomoc w oszczędzaniu 5 bajtów


Można zapisać 1 bajt, zmieniając whilena końcu na for(;;SDL_Flip(s));, i uważam, że możesz pominąć intmetodę Ci zaoszczędzić 4 kolejne.
pseudonim

4

JavaScript (ES6), 452 480 484 487 511 bajtów

Wow, stało się to dłużej niż oczekiwano:

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

Ta funkcja pobiera adres URL f('test.jpg');i zwraca wynik do elementu, canvasktóry jest dołączany do body.

Pamiętaj, że źródło musi znajdować się w tej samej domenie, w przeciwnym razie skrypt przestanie działać z powodu problemów z bezpieczeństwem.


Ograniczenia

Przetestowałem to w Firefoksie 42 na OS X (10.10) na maszynie z 2,5 GHz i7 i 16 GB pamięci RAM. Maksymalny rozmiar obrazu, jaki mogłem przetworzyć bez Firefoksa z prośbą o kontynuowanie wykonywania skryptu, to 1600 x 1932 px .


Bez golfa

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

Wydajność

Dla lepszego porównania wziąłem również „ American Gothic ” jako przykładowe źródło:

wprowadź opis zdjęcia tutaj


Edycje

  • Zapisano 24 bajty , używając for (a in b)zamiast for(;;). Dzięki ar34z
  • Zapisano 3 bajty , przechowując documentw zmiennej.
  • Zaoszczędzono 4 bajty , upuszczając niektóre z nich ().
  • Zaoszczędzono 10 bajtów , używając oznaczonych ciągów szablonów , pomijając tworzenie ()na obiekcie i usuwając kolejną parę nadmiarowych (). Dzięki apsillerom .
  • Zaoszczędzono 14 bajtów dzięki masowemu refaktoryzacji kodu, który spłaszcza tablicę kolorów po sortowaniu. Wielkie dzięki dla apsillerów i Ypnypn do podcięcia siebie.
  • Zaoszczędzono 1 bajt , refaktoryzując forpętlę, która otrzymuje kolory każdego piksela.

1
Możesz zminimalizować pętle for, za pomocą for(k in t)których zaoszczędzisz jeszcze kilka bajtów :)
ar34z

1
Dobra robota! Niektóre ulepszenia: stracić ()w new Image(); używaj otagowanych ciągów szablonów jako argumentów ciągów ( createElement`canvas`, getContext`2d`), nie używaj nawiasów dla parametrów funkcji pojedynczej strzałki (po prostu zrób f=u=>{...}; pareny są tylko dla funkcji strzałek z wieloma parametrami lub z zerowym parametrem). Ponadto możesz mieć jedną lub dwie forpętle pojedynczej instrukcji, które mają nawiasy, które nie są konieczne.
apsillery

Och, właściwie, dla funkcji strzałek z zerowym argumentem, używaj fałszywego argumentu jednoznakowego zamiast pustych znaków parens o dwóch znakach. ( i.onload=$=>...zamiast i.onload=()=>...)
apsillers

Myślę, że for(l in u)f.push(u[l]);może zostaćfor(z of u)f.push(z);
Ypnypn

@Ypnypn To może być nawet krótszy niż :). - for(u of t)for(z of u)f.push(z)jest dość krótki, ale można go jeszcze bardziej skrócić t.map(u=>u.map(z=>f.push(z))). W wielu przypadkach użycie .maplub użycie .somefunkcji strzałki będzie krótsze niż użycie forpętli. Jeśli chcesz naprawdę zwariować, możesz zaoszczędzić jeszcze więcej, dzięki t.map(u=>f.push.apply(f,u));czemu mówi „Dla każdej tablicy uw t, podaj ujako listę argumentów do f.pushvia apply(ponieważ pushmoże zaakceptować nieograniczoną liczbę argumentów i popycha je wszystkie w kolejności).
apsillers

4

Narzędzia 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) -

Zakłada się, że format wejścia / wyjścia ma format wyliczania pikseli ImageMagick .txt. Dane wejściowe są przekazywane jako nazwa pliku, a dane wyjściowe przechodzą do STDOUT.


Jeśli powyższe nie jest uważane za dobrze znany format obrazu, możemy dodać niezbędne konwersje:

Bash + narzędzia 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

Dane wejściowe i wyjściowe są określone jako nazwy plików. ImageMagick określa, jakie formaty plików mają być używane przez przekazywane rozszerzenia plików, dzięki czemu możemy używać dowolnych popularnych:

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

Wynikowy o.png wygląda następująco:

wprowadź opis zdjęcia tutaj


3

Python 2, 128 bajtów

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

Pod warunkiem, że obraz jest plikiem o nazwie abez rozszerzenia, wynikiem będzie plik o nazwie bbez rozszerzenia.

amerykański gotyk American Gothic (posortowane)


Nie sprawdziłem, ale a.putdata(sorted(b[f/c,f%d]for f in range(d*c)))zamiast tego powinieneś być w stanie zrobić coś podobnego (właśnie się obudziłem, więc mogłem pomieszać zmienne).
Kade

Jak napisałeś, nie działało (indeks poza zakresem), ale nie próbowałem przełączać żadnych zmiennych (w tej chwili nie mam dużo czasu). @Shebang
Zach Gates,

3

Java, 316 bajtów

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

Umieszcza wartości szesnastkowe kolorów pikseli w tablicy. Tablica jest sortowana, a kolory są ponownie mapowane na piksele obrazu. Nazwa wynikowego obrazu to a.png.

żółte tulipany żółte tulipany
Wkład amerykańskiego gotyku wprowadź opis zdjęcia tutaj


3

SmileBASIC, 39 35 bajtów

Zakładając, że obraz jest ładowany na stronę graficzną 512 * 512:

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

Wyjaśnił:

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

To takie proste! Niestety musimy użyć liczb całkowitych, które dodają 4 bajty do rozmiaru programu ze względu na przyrostki typu.


Nie jestem do końca pewien, dlaczego ints są wymagane. Wygląda na to, że użycie pływaków faktycznie poprawia wyniki. Uruchomienie tego kodu za pomocą ints on SYS/DEFSP.GRPumieszcza a FF000000w lewym górnym rogu ia w 00101010prawym dolnym rogu, co jest oczywistym przeciwieństwem pytania. Używanie pływaków umieszcza 00000000w lewym górnym rogu iw FFF8F8F8prawym dolnym rogu, co jest poprawne. (Oczywiście traktuje to kolory szesnastkowe jako niepodpisane / wyższy kanał większy, co prawdopodobnie jest poprawne.)
snail_

Myślę, że jest to poprawne, ponieważ pytanie nie określa konkretnego porządku sortowania (a ponieważ wartości są podpisane, 0xFF000000są mniejsze niż 0x00101010), ale tak naprawdę nie jestem pewien, dlaczego użyłem tutaj liczb całkowitych ... Myślę, że czas, kiedy nie rozumiałem, w jaki sposób GLOAD używał niepodpisanych wartości, kiedy używałeś tablicy zmiennoprzecinkowej i po prostu założyłem, że to nie działa.
maja

2

Java, 424 417 404 bajtów

To nie jest język, w którym chcesz grać w golfa ...

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 bajtów

Pierwszy post, pierwszy golf. Oczywiście nie jest najlepszy do gry w golfa

Niezupełnie przestrzegając orurowania. Bierze ścieżkę obrazu jako dane wejściowe i wysyła ją z literą „o” poprzedzającą nazwę.

Działa lepiej z bitmapami, wyniki kursów z innymi

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 bajtów

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

To korzysta z GDbiblioteki. Wykorzystanie f <filename>. Plik wejściowy musi mieć pngformat. Plik wyjściowy ma nazwę o.

Jak to działa: proste, tj. Przeczytaj zdjęcie, przejrzyj wszystkie współrzędne i uzyskaj piksele, posortuj piksele, ponownie przejdź przez współrzędne, ale tym razem ustaw piksele w kolejności, w jakiej pojawiają się na posortowanej liście, zapisz plik do dysk.

wprowadź opis zdjęcia tutaj

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.