code:
.element-animated {
  -webkit-animation-duration: 1s;
          animation-duration: 1s;
}

.element-animated.short {
  -webkit-animation-duration: .5s;
          animation-duration: .5s;
}

.element-animated.long {
  -webkit-animation-duration: 2s;
          animation-duration: 2s;
}

.element-animated.infinite {
  -webkit-animation-iteration-count: infinite;
          animation-iteration-count: infinite;
}

@-webkit-keyframes slide-from-right {
  0% {
    -webkit-transform: translateX(200px);
            transform: translateX(200px);
  }
  100% {
    -webkit-transform: none;
            transform: none;
  }
}

@keyframes slide-from-right {
  0% {
    -webkit-transform: translateX(200px);
            transform: translateX(200px);
  }
  100% {
    -webkit-transform: none;
            transform: none;
  }
}

.slide-from-right {
  -webkit-animation-name: slide-from-right;
          animation-name: slide-from-right;
}

@-webkit-keyframes slide-from-left {
  0% {
    -webkit-transform: translateX(-200px);
            transform: translateX(-200px);
  }
  100% {
    -webkit-transform: none;
            transform: none;
  }
}

@keyframes slide-from-left {
  0% {
    -webkit-transform: translateX(-200px);
            transform: translateX(-200px);
  }
  100% {
    -webkit-transform: none;
            transform: none;
  }
}

.slide-from-left {
  -webkit-animation-name: slide-from-left;
          animation-name: slide-from-left;
}

@-webkit-keyframes slide-from-top {
  0% {
    -webkit-transform: translateY(-200px);
            transform: translateY(-200px);
  }
  100% {
    -webkit-transform: none;
            transform: none;
  }
}

@keyframes slide-from-top {
  0% {
    -webkit-transform: translateY(-200px);
            transform: translateY(-200px);
  }
  100% {
    -webkit-transform: none;
            transform: none;
  }
}

.slide-from-top {
  -webkit-animation-name: slide-from-top;
          animation-name: slide-from-top;
}

@-webkit-keyframes slide-from-bottom {
  0% {
    -webkit-transform: translateY(200px);
            transform: translateY(200px);
  }
  100% {
    -webkit-transform: none;
            transform: none;
  }
}

@keyframes slide-from-bottom {
  0% {
    -webkit-transform: translateY(200px);
            transform: translateY(200px);
  }
  100% {
    -webkit-transform: none;
            transform: none;
  }
}

.slide-from-bottom {
  -webkit-animation-name: slide-from-bottom;
          animation-name: slide-from-bottom;
}

@-webkit-keyframes slide-from-right-bottom {
  0% {
    -webkit-transform: translate(100px, 100px);
            transform: translate(100px, 100px);
  }
  100% {
    -webkit-transform: none;
            transform: none;
  }
}

@keyframes slide-from-right-bottom {
  0% {
    -webkit-transform: translate(100px, 100px);
            transform: translate(100px, 100px);
  }
  100% {
    -webkit-transform: none;
            transform: none;
  }
}

.slide-from-right-bottom {
  -webkit-animation-name: slide-from-right-bottom;
          animation-name: slide-from-right-bottom;
}

@-webkit-keyframes slide-from-left-bottom {
  0% {
    -webkit-transform: translate(-100px, 100px);
            transform: translate(-100px, 100px);
  }
  100% {
    -webkit-transform: none;
            transform: none;
  }
}

@keyframes slide-from-left-bottom {
  0% {
    -webkit-transform: translate(-100px, 100px);
            transform: translate(-100px, 100px);
  }
  100% {
    -webkit-transform: none;
            transform: none;
  }
}

.slide-from-left-bottom {
  -webkit-animation-name: slide-from-left-bottom;
          animation-name: slide-from-left-bottom;
}

@-webkit-keyframes slide-from-right-top {
  0% {
    -webkit-transform: translate(100px, -100px);
            transform: translate(100px, -100px);
  }
  100% {
    -webkit-transform: none;
            transform: none;
  }
}

@keyframes slide-from-right-top {
  0% {
    -webkit-transform: translate(100px, -100px);
            transform: translate(100px, -100px);
  }
  100% {
    -webkit-transform: none;
            transform: none;
  }
}

.slide-from-right-top {
  -webkit-animation-name: slide-from-right-top;
          animation-name: slide-from-right-top;
}

@-webkit-keyframes slide-from-left-top {
  0% {
    -webkit-transform: translate(-100px, -100px);
            transform: translate(-100px, -100px);
  }
  100% {
    -webkit-transform: none;
            transform: none;
  }
}

@keyframes slide-from-left-top {
  0% {
    -webkit-transform: translate(-100px, -100px);
            transform: translate(-100px, -100px);
  }
  100% {
    -webkit-transform: none;
            transform: none;
  }
}

.slide-from-left-top {
  -webkit-animation-name: slide-from-left-top;
          animation-name: slide-from-left-top;
}

@-webkit-keyframes slide-left-right {
  0% {
    -webkit-transform: translateX(-100px);
            transform: translateX(-100px);
  }
  50% {
    -webkit-transform: none;
            transform: none;
  }
  100% {
    -webkit-transform: translateX(100px);
            transform: translateX(100px);
  }
}

@keyframes slide-left-right {
  0% {
    -webkit-transform: translateX(-100px);
            transform: translateX(-100px);
  }
  50% {
    -webkit-transform: none;
            transform: none;
  }
  100% {
    -webkit-transform: translateX(100px);
            transform: translateX(100px);
  }
}

.slide-left-right {
  -webkit-animation-name: slide-left-right;
          animation-name: slide-left-right;
  -webkit-animation-timing-function: linear;
          animation-timing-function: linear;
  -webkit-animation-direction: alternate;
          animation-direction: alternate;
  -webkit-animation-iteration-count: infinite;
          animation-iteration-count: infinite;
}

@-webkit-keyframes slide-top-bottom {
  0% {
    -webkit-transform: translateY(-100px);
            transform: translateY(-100px);
  }
  50% {
    -webkit-transform: none;
            transform: none;
  }
  100% {
    -webkit-transform: translateY(100px);
            transform: translateY(100px);
  }
}

@keyframes slide-top-bottom {
  0% {
    -webkit-transform: translateY(-100px);
            transform: translateY(-100px);
  }
  50% {
    -webkit-transform: none;
            transform: none;
  }
  100% {
    -webkit-transform: translateY(100px);
            transform: translateY(100px);
  }
}

.slide-top-bottom {
  -webkit-animation-name: slide-top-bottom;
          animation-name: slide-top-bottom;
  -webkit-animation-timing-function: linear;
          animation-timing-function: linear;
  -webkit-animation-direction: alternate;
          animation-direction: alternate;
  -webkit-animation-iteration-count: infinite;
          animation-iteration-count: infinite;
}

@-webkit-keyframes tremble {
  0%, 20%, 40%, 60%, 80%, 100% {
    -webkit-transform: translateX(5px);
            transform: translateX(5px);
  }
  10%, 30%, 50%, 70%, 90% {
    -webkit-transform: none;
            transform: none;
  }
}

@keyframes tremble {
  0%, 20%, 40%, 60%, 80%, 100% {
    -webkit-transform: translateX(5px);
            transform: translateX(5px);
  }
  10%, 30%, 50%, 70%, 90% {
    -webkit-transform: none;
            transform: none;
  }
}

.tremble {
  -webkit-animation-name: tremble;
          animation-name: tremble;
  -webkit-animation-timing-function: linear;
          animation-timing-function: linear;
  -webkit-animation-iteration-count: infinite;
          animation-iteration-count: infinite;
}

@-webkit-keyframes fade-out {
  0% {
    opacity: .8;
  }
  100% {
    opacity: 0;
  }
}

@keyframes fade-out {
  0% {
    opacity: .8;
  }
  100% {
    opacity: 0;
  }
}

.fade-out {
  -webkit-animation-name: fade-out;
          animation-name: fade-out;
}

@-webkit-keyframes fade-in {
  0% {
    opacity: 0;
  }
  100% {
    opacity: .8;
  }
}

@keyframes fade-in {
  0% {
    opacity: 0;
  }
  100% {
    opacity: .8;
  }
}

.fade-in {
  -webkit-animation-name: fade-in;
          animation-name: fade-in;
  -webkit-animation-timing-function: ease-in;
          animation-timing-function: ease-in;
}

@-webkit-keyframes pulsate {
  0%, 100% {
    opacity: .8;
  }
  50% {
    opacity: .6;
  }
}

@keyframes pulsate {
  0%, 100% {
    opacity: .8;
  }
  50% {
    opacity: .6;
  }
}

.pulsate {
  -webkit-animation-name: pulsate;
          animation-name: pulsate;
  -webkit-animation-timing-function: ease-in-out;
          animation-timing-function: ease-in-out;
  -webkit-animation-direction: alternate;
          animation-direction: alternate;
  -webkit-animation-iteration-count: infinite;
          animation-iteration-count: infinite;
}

@-webkit-keyframes rotate {
  0% {
    -webkit-transform: rotate(-1turn);
            transform: rotate(-1turn);
  }
  100% {
    -webkit-transform: none;
            transform: none;
  }
}

@keyframes rotate {
  0% {
    -webkit-transform: rotate(-1turn);
            transform: rotate(-1turn);
  }
  100% {
    -webkit-transform: none;
            transform: none;
  }
}

.rotate {
  -webkit-animation-name: rotate;
          animation-name: rotate;
  -webkit-animation-timing-function: ease-in-out;
          animation-timing-function: ease-in-out;
}

@-webkit-keyframes rotate-slide-from-right {
  0% {
    -webkit-transform: translateX(200px) rotate(1turn);
            transform: translateX(200px) rotate(1turn);
    -webkit-transform-origin: center;
            transform-origin: center;
  }
  100% {
    -webkit-transform: none;
            transform: none;
  }
}

@keyframes rotate-slide-from-right {
  0% {
    -webkit-transform: translateX(200px) rotate(1turn);
            transform: translateX(200px) rotate(1turn);
    -webkit-transform-origin: center;
            transform-origin: center;
  }
  100% {
    -webkit-transform: none;
            transform: none;
  }
}

.rotate-slide-from-right {
  -webkit-animation-name: rotate-slide-from-right;
          animation-name: rotate-slide-from-right;
  -webkit-animation-timing-function: ease-in-out;
          animation-timing-function: ease-in-out;
}

@-webkit-keyframes rotate-slide-from-left {
  0% {
    -webkit-transform: translateX(-200px) rotate(-1turn);
            transform: translateX(-200px) rotate(-1turn);
    -webkit-transform-origin: center;
            transform-origin: center;
  }
  100% {
    -webkit-transform: none;
            transform: none;
  }
}

@keyframes rotate-slide-from-left {
  0% {
    -webkit-transform: translateX(-200px) rotate(-1turn);
            transform: translateX(-200px) rotate(-1turn);
    -webkit-transform-origin: center;
            transform-origin: center;
  }
  100% {
    -webkit-transform: none;
            transform: none;
  }
}

.rotate-slide-from-left {
  -webkit-animation-name: rotate-slide-from-left;
          animation-name: rotate-slide-from-left;
  -webkit-animation-timing-function: ease-in-out;
          animation-timing-function: ease-in-out;
}

@-webkit-keyframes rotate-slide-from-top {
  0% {
    -webkit-transform: translateY(-200px) rotate(-1turn);
            transform: translateY(-200px) rotate(-1turn);
    -webkit-transform-origin: center;
            transform-origin: center;
  }
  100% {
    -webkit-transform: none;
            transform: none;
  }
}

@keyframes rotate-slide-from-top {
  0% {
    -webkit-transform: translateY(-200px) rotate(-1turn);
            transform: translateY(-200px) rotate(-1turn);
    -webkit-transform-origin: center;
            transform-origin: center;
  }
  100% {
    -webkit-transform: none;
            transform: none;
  }
}

.rotate-slide-from-top {
  -webkit-animation-name: rotate-slide-from-top;
          animation-name: rotate-slide-from-top;
  -webkit-animation-timing-function: ease-in-out;
          animation-timing-function: ease-in-out;
}

@-webkit-keyframes rotate-slide-from-bottom {
  0% {
    -webkit-transform: translateY(200px) rotate(-1turn);
            transform: translateY(200px) rotate(-1turn);
    -webkit-transform-origin: center;
            transform-origin: center;
  }
  100% {
    -webkit-transform: none;
            transform: none;
  }
}

@keyframes rotate-slide-from-bottom {
  0% {
    -webkit-transform: translateY(200px) rotate(-1turn);
            transform: translateY(200px) rotate(-1turn);
    -webkit-transform-origin: center;
            transform-origin: center;
  }
  100% {
    -webkit-transform: none;
            transform: none;
  }
}

.rotate-slide-from-bottom {
  -webkit-animation-name: rotate-slide-from-bottom;
          animation-name: rotate-slide-from-bottom;
  -webkit-animation-timing-function: ease-in-out;
          animation-timing-function: ease-in-out;
}

@-webkit-keyframes appear-from-right {
  0% {
    -webkit-transform: translateX(200px);
            transform: translateX(200px);
    opacity: 0;
  }
  40% {
    opacity: .7;
  }
  70% {
    opacity: 1;
  }
  100% {
    -webkit-transform: none;
            transform: none;
    opacity: 1;
  }
}

@keyframes appear-from-right {
  0% {
    -webkit-transform: translateX(200px);
            transform: translateX(200px);
    opacity: 0;
  }
  40% {
    opacity: .7;
  }
  70% {
    opacity: 1;
  }
  100% {
    -webkit-transform: none;
            transform: none;
    opacity: 1;
  }
}

.appear-from-right {
  -webkit-animation-name: appear-from-right;
          animation-name: appear-from-right;
  -webkit-animation-timing-function: cubic-bezier(0.39, 0.58, 0.57, 1);
          animation-timing-function: cubic-bezier(0.39, 0.58, 0.57, 1);
}

@-webkit-keyframes appear-from-left {
  0% {
    -webkit-transform: translateX(-200px);
            transform: translateX(-200px);
    opacity: 0;
  }
  40% {
    opacity: .7;
  }
  70% {
    opacity: 1;
  }
  100% {
    -webkit-transform: none;
            transform: none;
    opacity: 1;
  }
}

@keyframes appear-from-left {
  0% {
    -webkit-transform: translateX(-200px);
            transform: translateX(-200px);
    opacity: 0;
  }
  40% {
    opacity: .7;
  }
  70% {
    opacity: 1;
  }
  100% {
    -webkit-transform: none;
            transform: none;
    opacity: 1;
  }
}

.appear-from-left {
  -webkit-animation-name: appear-from-left;
          animation-name: appear-from-left;
  -webkit-animation-timing-function: cubic-bezier(0.39, 0.58, 0.57, 1);
          animation-timing-function: cubic-bezier(0.39, 0.58, 0.57, 1);
}

@-webkit-keyframes appear-from-top {
  0% {
    -webkit-transform: translateY(-200px);
            transform: translateY(-200px);
    opacity: 0;
  }
  40% {
    opacity: .7;
  }
  70% {
    opacity: 1;
  }
  100% {
    -webkit-transform: none;
            transform: none;
    opacity: 1;
  }
}

@keyframes appear-from-top {
  0% {
    -webkit-transform: translateY(-200px);
            transform: translateY(-200px);
    opacity: 0;
  }
  40% {
    opacity: .7;
  }
  70% {
    opacity: 1;
  }
  100% {
    -webkit-transform: none;
            transform: none;
    opacity: 1;
  }
}

.appear-from-top {
  -webkit-animation-name: appear-from-top;
          animation-name: appear-from-top;
  -webkit-animation-timing-function: cubic-bezier(0.39, 0.58, 0.57, 1);
          animation-timing-function: cubic-bezier(0.39, 0.58, 0.57, 1);
}

@-webkit-keyframes appear-from-bottom {
  0% {
    -webkit-transform: translateY(200px);
            transform: translateY(200px);
    opacity: 0;
  }
  40% {
    opacity: .7;
  }
  70% {
    opacity: 1;
  }
  100% {
    -webkit-transform: none;
            transform: none;
    opacity: 1;
  }
}

@keyframes appear-from-bottom {
  0% {
    -webkit-transform: translateY(200px);
            transform: translateY(200px);
    opacity: 0;
  }
  40% {
    opacity: .7;
  }
  70% {
    opacity: 1;
  }
  100% {
    -webkit-transform: none;
            transform: none;
    opacity: 1;
  }
}

.appear-from-bottom {
  -webkit-animation-name: appear-from-bottom;
          animation-name: appear-from-bottom;
  -webkit-animation-timing-function: cubic-bezier(0.39, 0.58, 0.57, 1);
          animation-timing-function: cubic-bezier(0.39, 0.58, 0.57, 1);
}


code:
.element-animated {
  animation-duration: 1s;

  &.short {
    animation-duration: .5s;
  }

  &.long {
    animation-duration: 2s;
  }

  &.infinite {
    animation-iteration-count: infinite;
  }
}

license: mit


code:
$appear-from-bottom-offset: $default-offset;

@keyframes appear-from-bottom {

  0% {
    transform: translateY($appear-from-bottom-offset);
    opacity: 0;
  }

  40% {
    opacity: .7;
  }

  70% {
    opacity: 1;
  }

  100% {
    transform: none;
    opacity: 1;
  }
}

.appear-from-bottom {
  animation-name: appear-from-bottom;
  animation-timing-function: cubic-bezier(.39,.58,.57,1);
}


code:
$appear-from-left-offset: $default-offset;

@keyframes appear-from-left {

  0% {
    transform: translateX(-$appear-from-left-offset);
    opacity: 0;
  }

  40% {
    opacity: .7;
  }

  70% {
    opacity: 1;
  }

  100% {
    transform: none;
    opacity: 1;
  }
}

.appear-from-left {
  animation-name: appear-from-left;
  animation-timing-function: cubic-bezier(.39,.58,.57,1);
}


code:
$appear-from-right-offset: $default-offset;

@keyframes appear-from-right {

  0% {
    transform: translateX($appear-from-right-offset);
    opacity: 0;
  }

  40% {
    opacity: .7;
  }

  70% {
    opacity: 1;
  }

  100% {
    transform: none;
    opacity: 1;
  }
}

.appear-from-right {
  animation-name: appear-from-right;
  animation-timing-function: cubic-bezier(.39,.58,.57,1);
}


code:
$appear-from-top-offset: $default-offset;

@keyframes appear-from-top {

  0% {
    transform: translateY(-$appear-from-top-offset);
    opacity: 0;
  }

  40% {
    opacity: .7;
  }

  70% {
    opacity: 1;
  }

  100% {
    transform: none;
    opacity: 1;
  }
}

.appear-from-top {
  animation-name: appear-from-top;
  animation-timing-function: cubic-bezier(.39,.58,.57,1);
}


code:
@keyframes fade-in {

  0% {
    opacity: 0;
  }

  100% {
    opacity: .8;
  }
}

.fade-in {
  animation-name: fade-in;
  animation-timing-function: ease-in;
}


code:
@keyframes fade-out {

  0% {
    opacity: .8;
  }

  100% {
    opacity: 0;
  }
}

.fade-out {
  animation-name: fade-out;
}


code:
@keyframes pulsate {

  0%, 100%  {
    opacity: .8;
  }

  50%  {
    opacity: .6;
  }
}

.pulsate {
  animation-name: pulsate;
  animation-timing-function: ease-in-out;
  animation-direction: alternate;
  animation-iteration-count: infinite;
}


code:
$rotate-slide-from-bottom-offset: $default-offset;

@keyframes rotate-slide-from-bottom {

  0% {
    transform: translateY($rotate-slide-from-bottom-offset) rotate(-1turn) ;
    transform-origin: center;
  }

  100%  {
    transform: none;
  }
}

.rotate-slide-from-bottom {
  animation-name: rotate-slide-from-bottom;
  animation-timing-function: ease-in-out;
}


code:
$rotate-slide-from-left-offset: $default-offset;

@keyframes rotate-slide-from-left {

  0% {
    transform: translateX(-$rotate-slide-from-left-offset) rotate(-1turn);
    transform-origin: center;
  }

  100%  {
    transform: none;
  }
}

.rotate-slide-from-left {
  animation-name: rotate-slide-from-left;
  animation-timing-function: ease-in-out;
}


code:
$rotate-slide-from-right-offset: $default-offset;

@keyframes rotate-slide-from-right {

  0% {
    transform: translateX($rotate-slide-from-right-offset) rotate(1turn);
    transform-origin: center;
  }

  100%  {
    transform: none;
  }
}

.rotate-slide-from-right {
  animation-name: rotate-slide-from-right;
  animation-timing-function: ease-in-out;
}


code:
$rotate-slide-from-top-offset: $default-offset;

@keyframes rotate-slide-from-top {

  0% {
    transform: translateY(-$rotate-slide-from-top-offset) rotate(-1turn);
    transform-origin: center;
  }

  100%  {
    transform: none;
  }
}

.rotate-slide-from-top {
  animation-name: rotate-slide-from-top;
  animation-timing-function: ease-in-out;
}


code:
@keyframes rotate {

  0% {
    transform: rotate(-1turn);
  }

  100%  {
    transform: none;
  }
}

.rotate {
  animation-name: rotate;
  animation-timing-function: ease-in-out;
}


code:
$slide-from-bottom-offset: $default-offset;

@keyframes slide-from-bottom {

  0% {
    transform: translateY($slide-from-bottom-offset);
  }

  100% {
    transform: none;
  }
}

.slide-from-bottom {
  animation-name: slide-from-bottom;
}


code:
$slide-from-left-bottom-offset: $default-offset/2;

@keyframes slide-from-left-bottom {

  0% {
    transform: translate(-$slide-from-left-bottom-offset, $slide-from-left-bottom-offset);
  }

  100% {
    transform: none;
  }
}

.slide-from-left-bottom {
  animation-name: slide-from-left-bottom;
}


code:
$slide-from-left-top-offset: $default-offset/2;

@keyframes slide-from-left-top {

  0% {
    transform: translate(-$slide-from-left-top-offset, -$slide-from-left-top-offset);
  }

  100% {
    transform: none;
  }
}

.slide-from-left-top {
  animation-name: slide-from-left-top;
}


code:
$slide-from-left-offset: $default-offset;

@keyframes slide-from-left {

  0% {
    transform: translateX(-$slide-from-left-offset);
  }

  100% {
    transform: none;
  }
}

.slide-from-left {
  animation-name: slide-from-left;
}


code:
$slide-from-right-bottom-offset: $default-offset/2;

@keyframes slide-from-right-bottom {

  0% {
    transform: translate($slide-from-right-bottom-offset, $slide-from-right-bottom-offset);
  }

  100% {
    transform: none;
  }
}

.slide-from-right-bottom {
  animation-name: slide-from-right-bottom;
}


code:
$slide-from-right-top-offset: $default-offset/2;

@keyframes slide-from-right-top {

  0% {
    transform: translate($slide-from-right-top-offset, -$slide-from-right-top-offset);
  }

  100% {
    transform: none;
  }
}

.slide-from-right-top {
  animation-name: slide-from-right-top;
}


code:
$slide-from-right-offset: $default-offset;

@keyframes slide-from-right {

  0% {
    transform: translateX($slide-from-right-offset);
  }

  100% {
    transform: none;
  }
}

.slide-from-right {
  animation-name: slide-from-right;
}


code:
$slide-from-top-offset: $default-offset;

@keyframes slide-from-top {

  0% {
    transform: translateY(-$slide-from-top-offset);
  }

  100% {
    transform: none;
  }
}

.slide-from-top {
  animation-name: slide-from-top;
}


code:
$slide-left-right-offset: $default-offset/2;

@keyframes slide-left-right {

  0% {
    transform: translateX(-$slide-left-right-offset);
  }

  50% {
    transform: none;
  }

  100% {
    transform: translateX($slide-left-right-offset)
  }
}

.slide-left-right {
  animation-name: slide-left-right;
  animation-timing-function: linear;
  animation-direction: alternate;
  animation-iteration-count: infinite;
}


code:
$slide-top-bottom-offset: $default-offset/2;

@keyframes slide-top-bottom {

  0% {
    transform: translateY(-$slide-left-right-offset);
  }

  50% {
    transform: none;
  }

  100% {
    transform: translateY($slide-left-right-offset)
  }
}

.slide-top-bottom {
  animation-name: slide-top-bottom;
  animation-timing-function: linear;
  animation-direction: alternate;
  animation-iteration-count: infinite;
}


code:
$tremble-offset: 5px;

@keyframes tremble {

  0%, 20%, 40%, 60%, 80%, 100%  {
    transform: translateX($tremble-offset);
  }

  10%, 30%, 50%, 70%, 90% {
    transform: none;
  }
}

.tremble {
  animation-name: tremble;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}


code:
$default-offset: 200px;

@import 'animations/animation-setup';

@import 'animations/slide-from-right';
@import 'animations/slide-from-left';
@import 'animations/slide-from-top';
@import 'animations/slide-from-bottom';
@import 'animations/slide-from-right-bottom';
@import 'animations/slide-from-left-bottom';
@import 'animations/slide-from-right-top';
@import 'animations/slide-from-left-top';
@import 'animations/slide-left-right';
@import 'animations/slide-top-bottom';
@import 'animations/tremble';
@import 'animations/fade-out';
@import 'animations/fade-in';
@import 'animations/pulsate';
@import 'animations/rotate';
@import 'animations/rotate-slide-from-right';
@import 'animations/rotate-slide-from-left';
@import 'animations/rotate-slide-from-top';
@import 'animations/rotate-slide-from-bottom';
@import 'animations/appear-from-right';
@import 'animations/appear-from-left';
@import 'animations/appear-from-top';
@import 'animations/appear-from-bottom';


code:
{% load static %}
{% load main_filters %}

<!DOCTYPE html>

    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="{% static 'css/style.css' %}">
    <link rel="stylesheet" href="{% static 'css/list_of_books.css' %}">
    <link rel="stylesheet" href="{% static 'css/book_search_window.css' %}">
    <link rel="stylesheet" href="{% static 'css/book_big.css' %}">
    <link rel="stylesheet" href="{% static 'css/contents_of_book.css' %}">
    <link rel="stylesheet" type="text/css" href="{% static 'node_modules/repaintless/repaintless-css/repaintless.css' %}" />
    <link rel="stylesheet" type="text/css" href="{% static 'node_modules/all-animation/dist/all-animation.css' %}" />
    <link rel="stylesheet" type="text/css" href="{% static 'node_modules/hover.css/css/hover.css' %}" rel="stylesheet" media="all" />

                    readBooks

                    Библиотека

                    Избранное

                    Авторы

                    Открыть книгу
                    {% include 'includes/book_search_window.html' %}

                    Добавить книгу

            {% block content %}
                content
            {% endblock content %}


code:
            {{ book.title }}

            {{ book.description|truncatewords:40 }}

                Перейти к книге

                Автор: {{ book.author }}

                Группа: {{ book.group }}


code: <input type="text" data-book-search="true" placeholder="Введите название книги">


code:
    {% for book in books %}
        {% include 'includes/book.html' %}
    {% endfor %}


code:
.gradle
/local.properties
.DS_Store
build
*.iml
.idea
/captures
/projectFilesBackup
.externalNativeBuild
javah
.cxx/

repo_name: tachiyomiorg/mirror

path: .gitignore


code:
[submodule "library/src/main/jni/jpeg/libjpeg-turbo"]
	path = library/src/main/jni/jpeg/libjpeg-turbo
	url = https://github.com/seven332/libjpeg-turbo.git
[submodule "library/src/main/jni/png/libpng"]
	path = library/src/main/jni/png/libpng
	url = https://github.com/seven332/libpng.git
[submodule "library/src/main/jni/gif/giflib"]
	path = library/src/main/jni/gif/giflib
	url = https://github.com/seven332/giflib.git
[submodule "library/src/main/jni/test/check"]
	path = library/src/main/jni/test/check
	url = https://github.com/seven332/check-android-binaries.git
[submodule "library/src/main/jni/webp/libwebp"]
	path = library/src/main/jni/webp/libwebp
	url = https://chromium.googlesource.com/webm/libwebp

path: .gitmodules


code:
Image
Copyright 2015-2016 Hippo Seven
Released under the Apache License 2.0

An image library with data stored in native heap on Android.

香风智乃是最可爱的女孩子。
chino kafuu is the cutest girl.

path: NOTICE


code:
# Image

Image 是把图像数据放到 native heap 的 Android 图像库，可以降低 OutOfMemoryError 的出现概率。支持 JPEG，PNG（包括 apng）和 GIF（包括动态图）。

Image is an Android image library with data stored in native heap. JPEG, PNG(include apng) and GIF(include animated) supported.

# Usage

在最外面的 `build.gradle` 里加上 jitpack，别加到 buildscript 里了。

Add jitpack repository in top `build.gradle`, DO **NOT** ADD IT TO buildscript.

    allprojects {
        repositories {
            ...
            maven { url "https://jitpack.io" }
        }
    }

## Single library

TODO: 中文翻译。 在项目 `build.gradle` 里添加 Image 依赖。

Image is available as a single library with all codecs included. Add Image as dependency in project `build.gradle`.

    dependencies {
        ...
        compile 'com.github.seven332.Image:singlelib:0.3.0'
    }

## Multi library

TODO: 中文翻译。

Image is also available as multiple libraries. Include the core, then add on whichever codecs you need.

Note: If you need every image codec, use the single library release above.

    dependencies {
        ...
        implementation 'com.github.seven332.Image:core:0.3.0'
        implementation 'com.github.seven332.Image:jpeg:0.3.0' // Optional
        implementation 'com.github.seven332.Image:png:0.3.0'  // Optional
        implementation 'com.github.seven332.Image:gif:0.3.0'  // Optional
    }

# License

    Copyright (C) 2015-2018 Hippo Seven

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.


code:
/*
 * Copyright 2017 Hippo Seven
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

apply plugin: 'com.github.dcendents.android-maven'

tasks.withType(JavaCompile) {
    options.encoding = 'UTF-8'
}

tasks.withType(Javadoc) {
    options.encoding = 'UTF-8'
    failOnError false
}

// build a jar with source files
task sourcesJar(type: Jar) {
    from android.sourceSets.main.java.srcDirs
    classifier = 'sources'
}

task javadoc(type: Javadoc) {
    source = android.sourceSets.main.java.sourceFiles
    classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
    classpath += configurations.compile
}

// build a jar with javadoc
task javadocJar(type: Jar, dependsOn: javadoc) {
    classifier = 'javadoc'
    from javadoc.destinationDir
}

artifacts {
    archives sourcesJar
    archives javadocJar
}


code:
apply plugin: 'com.android.application'

android {
    compileSdkVersion rootProject.ext.compileSdkVersion
    buildToolsVersion rootProject.ext.buildToolsVersion

    defaultConfig {
        applicationId "com.hippo.image.example"
        minSdkVersion 21
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation project(":singlelib")
    implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibraryVersion}"
    implementation "com.android.support:support-v4:${rootProject.ext.supportLibraryVersion}"
    implementation "com.android.support:recyclerview-v7:${rootProject.ext.supportLibraryVersion}"
    implementation "com.github.seven332:konwidget:${rootProject.ext.konwidgetVersion}"
    implementation "com.github.seven332:yorozuya:${rootProject.ext.yorozuyaVersion}"
    testImplementation "junit:junit:${rootProject.ext.junitVersion}"
}


code:
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in E:\Android\android-sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}


code:
package com.hippo.image.example;

import android.app.Application;
import android.test.ApplicationTestCase;

/**
 * Testing Fundamentals
 */
public class ApplicationTest extends ApplicationTestCase<Application> {
    public ApplicationTest() {
        super(Application.class);
    }
}


code:
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.hippo.image.example"
          xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".TestActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>

</manifest>


code:
/*
 * Copyright 2016 Hippo Seven
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.hippo.image.example;

/*
 * Created by Hippo on 8/19/2016.
 */

import android.graphics.Bitmap;
import android.support.annotation.Nullable;

import com.hippo.image.ImageData;
import com.hippo.image.ImageRenderer;

/**
 * This class uses {@link Bitmap} to represent {@link ImageRenderer}.
 * Call {@link #getBitmap()} to get the bitmap.
 */
public class ImageBitmap {

    private int mRatio;
    private int mWidth;
    private int mHeight;
    private int mFormat;
    private boolean mOpaque;
    private int mFrameCount;
    private int[] mDelayArray;
    private int mByteCount;

    @Nullable
    private Bitmap mBitmap;
    @Nullable
    private ImageRenderer mImageRenderer;

    /**
     * The ImageData must be completed and
     * ratio <= width && ratio <= height, or throw
     * IllegalStateException.
     */
    public ImageBitmap(ImageData imageData, int ratio) {
        // Only completed image supported
        if (!imageData.isCompleted()) {
            throw new IllegalStateException("ImageBitmap can only handle completed ImageData");
        }

        int width = imageData.getWidth();
        int height = imageData.getHeight();
        if (ratio > width || ratio > height) {
            throw new IllegalStateException("Ratio is too big");
        }

        mRatio = ratio;
        mWidth = width / ratio;
        mHeight = height / ratio;
        mFormat = imageData.getFormat();
        mOpaque = imageData.isOpaque();
        mFrameCount = imageData.getFrameCount();
        mByteCount = imageData.getByteCount();
        mDelayArray = new int[mFrameCount];
        for (int i = 0; i < mFrameCount; i++) {
            mDelayArray[i] = imageData.getDelay(i);
        }
        mBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);

        // Render first frame
        ImageRenderer imageRenderer = imageData.createImageRenderer();
        imageRenderer.reset();
        imageRenderer.render(mBitmap, 0, 0, 0, 0, width, height, ratio, false, 0);

        if (mFrameCount == 1) {
            // Recycle image renderer if it is not animated
            imageRenderer.recycle();
        } else {
            // Store image renderer if it is animated
            mImageRenderer = imageRenderer;
        }
    }

    /**
     * Recycle the Bitmap and ImageRenderer.
     */
    public void recycle() {
        if (mBitmap != null) {
            mBitmap.recycle();
            mBitmap = null;
        }
        if (mImageRenderer != null) {
            mImageRenderer.recycle();
            mBitmap = null;
        }
    }

    /**
     * Draw first frame to bitmap.
     */
    public void reset() {
        if (mBitmap != null && mImageRenderer != null) {
            mImageRenderer.reset();
            mImageRenderer.render(mBitmap, 0, 0, 0, 0, mWidth, mHeight, mRatio, false, 0);
        }
    }

    /**
     * Draw next frame to bitmap.
     */
    public void advance() {
        if (mBitmap != null && mImageRenderer != null) {
            mImageRenderer.advance();
            mImageRenderer.render(mBitmap, 0, 0, 0, 0, mWidth, mHeight, mRatio, false, 0);
        }
    }

    /**
     * Get the Bitmap.
     */
    @Nullable
    public Bitmap getBitmap() {
        return mBitmap;
    }

    /**
     * Return the ratio.
     */
    public int getRatio() {
        return mRatio;
    }

    /**
     * Return the width of the Bitmap.
     */
    public int getWidth() {
        return mWidth;
    }

    /**
     * Return the height of the Bitmap.
     */
    public int getHeight() {
        return mHeight;
    }

    /**
     * Return the format of the ImageData.
     */
    public int getFormat() {
        return mFormat;
    }

    /**
     * Return true if the ImageData is opaque.
     */
    public boolean isOpaque() {
        return mOpaque;
    }

    /**
     * Return the frame count of the ImageData.
     */
    public int getFrameCount() {
        return mFrameCount;
    }

    /**
     * Return true if it is animated image
     */
    public boolean isAnimated() {
        return mFrameCount > 1;
    }

    /**
     * Return the delay of the frame.
     */
    public int getDelay(int frame) {
        return mDelayArray[frame];
    }

    /**
     * Return delay of current delay.
     * Return 0 if current frame is invalid.
     */
    public int getCurrentDelay() {
        if (mImageRenderer != null) {
            return mImageRenderer.getCurrentDelay();
        } else {
            return Integer.MAX_VALUE;
        }
    }

    /**
     * Return the byte count of the ImageData.
     */
    public int getByteCount() {
        return mByteCount;
    }
}


code:
/*
 * Copyright 2016 Hippo Seven
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.hippo.image.example;

/*
 * Created by Hippo on 8/19/2016.
 */

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.Drawable;
import android.os.SystemClock;
import android.support.annotation.NonNull;

public class ImageDrawable extends Drawable implements Animatable, Runnable {

    private ImageBitmap mImageBitmap;
    private Paint mPaint;

    /** Whether the drawable has an animation callback posted. */
    private boolean mRunning;

    /** Whether the drawable should animate when visible. */
    private boolean mAnimating;

    public ImageDrawable(@NonNull ImageBitmap imageBitmap) {
        mImageBitmap = imageBitmap;
        mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
    }

    public ImageBitmap getImageBitmap() {
        return mImageBitmap;
    }

    @Override
    public boolean setVisible(boolean visible, boolean restart) {
        final boolean changed = super.setVisible(visible, restart);
        if (mImageBitmap.isAnimated()) {
            if (visible) {
                if (restart || changed) {
                    setFrame(!(restart || !mRunning), true, mAnimating);
                }
            } else {
                unscheduleSelf(this);
            }
        }
        return changed;
    }

    /**
     * Starts the animation, looping if necessary. This method has no effect
     * if the animation is running.
     *

     * Note: Do not call this in the
     * {@link android.app.Activity#onCreate} method of your activity, because
     * the {@link AnimationDrawable} is not yet fully attached to the window.
     * If you want to play the animation immediately without requiring
     * interaction, then you might want to call it from the
     * {@link android.app.Activity#onWindowFocusChanged} method in your
     * activity, which will get called when Android brings your window into
     * focus.
     *
     * @see #isRunning()
     * @see #stop()
     */
    @Override
    public void start() {
        mAnimating = true;

        if (mImageBitmap.isAnimated() && !isRunning()) {
            // Start from 0th frame.
            setFrame(false, false, true);
        }
    }

    /**
     * Stops the animation. This method has no effect if the animation is not
     * running.
     *
     * @see #isRunning()
     * @see #start()
     */
    @Override
    public void stop() {
        mAnimating = false;

        if (mImageBitmap.isAnimated() && isRunning()) {
            unscheduleSelf(this);
        }
    }

    /**
     * Indicates whether the animation is currently running or not.
     *
     * @return true if the animation is running, false otherwise
     */
    @Override
    public boolean isRunning() {
        return mRunning;
    }

    /**
     * This method exists for implementation purpose only and should not be
     * called directly. Invoke {@link #start()} instead.
     *
     * @see #start()
     */
    @Override
    public void run() {
        setFrame(true, false, true);
    }

    private void setFrame(boolean resetOrNext, boolean unschedule, boolean animate) {
        mAnimating = animate;
        if (resetOrNext) {
            mImageBitmap.advance();
        } else  {
            mImageBitmap.reset();
        }
        invalidateSelf();
        if (unschedule || animate) {
            unscheduleSelf(this);
        }
        if (animate) {
            mRunning = true;
            scheduleSelf(this, SystemClock.uptimeMillis() + mImageBitmap.getCurrentDelay());
        }
    }

    @Override
    public void unscheduleSelf(@NonNull Runnable what) {
        mRunning = false;
        super.unscheduleSelf(what);
    }

    @Override
    public void draw(@NonNull Canvas canvas) {
        final Bitmap bitmap = mImageBitmap.getBitmap();
        if (bitmap != null) {
            canvas.drawBitmap(bitmap, null, getBounds(), mPaint);
        }
    }

    @Override
    public void setAlpha(int alpha) {
        mPaint.setAlpha(alpha);
    }

    @Override
    public void setColorFilter(ColorFilter colorFilter) {
        mPaint.setColorFilter(colorFilter);
    }

    @Override
    public int getOpacity() {
        return mImageBitmap.isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
    }

    @Override
    public int getIntrinsicWidth() {
        return mImageBitmap.getWidth();
    }

    @Override
    public int getIntrinsicHeight() {
        return mImageBitmap.getHeight();
    }
}


code:
package com.hippo.image.example;

import android.graphics.Bitmap;
import android.graphics.Rect;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.ImageView;

import com.hippo.image.BitmapDecoder;
import com.hippo.image.BitmapRegionDecoder;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ImageView imageView = (ImageView) findViewById(R.id.image);

        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(getResources().openRawResource(R.raw.jpeg_ycck));

        Rect rect = new Rect(200, 200, 500, 500);
        Bitmap bitmap = decoder.decodeRegion(rect, BitmapDecoder.CONFIG_AUTO, 3);
        imageView.setImageBitmap(bitmap);

        /*
        ImageData data = Image.decode(getResources().openRawResource(R.raw.skip_frist), false);
        final ImageDrawable drawable = new ImageDrawable(new ImageBitmap(data, 1));
        drawable.start();
        imageView.setImageDrawable(drawable);
        */

        //testFormatSupported();
        //testAPNG();
        //testGIF();

        /*
        final ImageInfo info = new ImageInfo();
        final boolean result = BitmapDecoder.decode(getResources().openRawResource(R.raw.jpeg_cmyk), info);
        if (result) {
            Log.d("TAG", "width = " + info.width);
            Log.d("TAG", "height = " + info.height);
            Log.d("TAG", "format = " + info.format);
            Log.d("TAG", "opaque = " + info.opaque);
            Log.d("TAG", "frameCount = " + info.frameCount);
        } else {
            Log.d("TAG", "result false");
        }
        */

        //final long t0 = System.nanoTime();
        //Bitmap bitmap = BitmapDecoder.decode(getResources().openRawResource(R.raw.skip_frist), BitmapDecoder.CONFIG_ARGB_8888, 1);

        //BitmapFactory.Options options = new BitmapFactory.Options();
        //options.inPreferredConfig = Bitmap.Config.RGB_565;
        //Bitmap bitmap = BitmapFactory.decodeStream(getResources().openRawResource(R.raw.skip_frist), null, options);

        //final long t1 = System.nanoTime();

        //Log.d("TAG", "" + ((t1 - t0) / 1000000));
    }

}


code:
/*
 * Copyright 2016 Hippo Seven
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.hippo.image.example;

/*
 * Created by Hippo on 9/16/2016.
 */

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Debug;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

import com.hippo.image.BitmapDecoder;
import com.hippo.image.BitmapRegionDecoder;
import com.hippo.image.Image;
import com.hippo.image.ImageData;
import com.hippo.image.ImageInfo;
import com.hippo.image.ImageRenderer;
import com.hippo.yorozuya.FileUtils;
import com.hippo.yorozuya.MathUtils;

import junit.framework.Assert;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;

public class TestActivity extends AppCompatActivity implements Runnable {

    private static final String LOG_TAG = TestActivity.class.getSimpleName();

    private static class ImageRes {

        public int id;
        public int width;
        public int height;
        public int format;
        public boolean opaque;
        public int frameCount;

        public ImageRes(int id, int width, int height, int format, boolean opaque, int frameCount) {
            this.id = id;
            this.width = width;
            this.height = height;
            this.format = format;
            this.opaque = opaque;
            this.frameCount = frameCount;
        }
    }

    private static final ImageRes[] IMAGES = {
            new ImageRes(R.raw.apng_rgb,  580,  584,  Image.FORMAT_PNG,  true,  1),
            new ImageRes(R.raw.apng_rgba, 100,  100,  Image.FORMAT_PNG,  false, 20),
            new ImageRes(R.raw.gif,       540,  540,  Image.FORMAT_GIF,  false, -1),
            new ImageRes(R.raw.jpeg_cmyk, 256,  256,  Image.FORMAT_JPEG, true,  1),
            new ImageRes(R.raw.jpeg_ycck, 1713, 2479, Image.FORMAT_JPEG, true,  1),
            new ImageRes(R.raw.png_rgba,  800,  600,  Image.FORMAT_PNG,  false,  1),
            new ImageRes(R.raw.png_test,  300,  300,  Image.FORMAT_PNG,  false,  1),
    };

    private static final int[] CONFIGS = {
            BitmapDecoder.CONFIG_AUTO,
            BitmapDecoder.CONFIG_RGB_565,
            BitmapDecoder.CONFIG_RGBA_8888,
    };

    private static final int[] RATIOS = {
            1, 2, 3, 4, 5, 6, 7, 8, 9,
    };

    private Resources mResources;
    private File mDir;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mResources = getResources();
        mDir = getExternalFilesDir("test");
        FileUtils.deleteContent(mDir);

        new Thread(this).start();
    }

    private String configToString(int config) {
        switch (config) {
            case BitmapDecoder.CONFIG_AUTO:
                return "AUTO";
            case BitmapDecoder.CONFIG_RGB_565:
                return "RGB_565";
            case BitmapDecoder.CONFIG_RGBA_8888:
                return "RGBA_8888";
            default:
                return "Unknown_config";
        }
    }

    @Override
    public void run() {
        test();
    }

    private void test() {
        testImage();
        testBitmapDecoderDecodeInfo();
        testBitmapDecoderDecode();
        testBitmapRegionDecoderDecode();
        Log.d(LOG_TAG, "Test Done!");
    }

    private void testImage() {
        for (ImageRes res : IMAGES) {
            Log.d(LOG_TAG, "testImage " + mResources.getResourceEntryName(res.id));
            for (int i = 0; i < 2; i++) {
                final ImageData imageData;
                if (i == 0) {
                    imageData = Image.decode(mResources.openRawResource(res.id), false);
                    Assert.assertNotNull(imageData);
                } else {
                    imageData = Image.decode(mResources.openRawResource(res.id), true);
                    Assert.assertNotNull(imageData);
                    imageData.complete();
                }
                final ImageRenderer imageRenderer = imageData.createImageRenderer();

                final int h1 = imageData.getWidth() / 4;
                final int h2 = imageData.getWidth() / 3;
                final int v1 = imageData.getHeight() / 4;
                final int v2 = imageData.getHeight() / 3;
                final Rect rect = new Rect();

                for (int j = 0; j < imageData.getFrameCount() + 1; j++) {
                    for (int ratio : RATIOS) {
                        for (int left = 0; left < imageData.getWidth(); left += MathUtils.random(h1, h2)) {
                            for (int right = left + MathUtils.random(h1, h2); right < imageData.getWidth(); right += MathUtils.random(h1, h2)) {
                                for (int top = 0; top < imageData.getHeight(); top += MathUtils.random(v1, v2)) {
                                    for (int bottom = top + MathUtils.random(v1, v2); bottom < imageData.getHeight(); bottom += MathUtils.random(v1, v2)) {
                                        rect.set(left, top, right, bottom);
                                        testImage1(res.id, imageRenderer, rect, ratio);
                                    }
                                }
                            }
                        }
                    }
                    imageRenderer.advance();
                }

                imageRenderer.recycle();
                imageData.recycle();
                Log.i(LOG_TAG, "" + FileUtils.readableByteCount(Debug.getNativeHeapAllocatedSize(), false));
            }
        }
    }

    private String getImageName(int id, Rect rect, int ratio) {
        final String idName = mResources.getResourceEntryName(id);
        return "image_" + idName + "_" + rectToString(rect) + "_ratio-" + ratio;
    }

    private void testImage1(int id, ImageRenderer renderer, Rect rect, int ratio) {
        final String name = getImageName(id, rect, ratio);
        Bitmap bitmap = Bitmap.createBitmap(rect.width() / ratio, rect.height() / ratio, Bitmap.Config.ARGB_8888);
        renderer.render(bitmap, 0, 0, rect.left, rect.top, rect.width(), rect.height(), ratio, true, Color.TRANSPARENT);
        try {
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, new FileOutputStream(new File(mDir, name + ".png")));
        } catch (FileNotFoundException e) {
            Log.e(LOG_TAG, "Can't save BitmapDecoder decode " + name, e);
        }
        bitmap.recycle();
    }

    private void testBitmapDecoderDecodeInfo() {
        for (ImageRes imageRes : IMAGES) {
            Log.d(LOG_TAG, "testBitmapDecoderDecodeInfo " + mResources.getResourceEntryName(imageRes.id));

            final ImageInfo info = new ImageInfo();
            BitmapDecoder.decode(mResources.openRawResource(imageRes.id), info);
            Assert.assertEquals(imageRes.width, info.width);
            Assert.assertEquals(imageRes.height, info.height);
            Assert.assertEquals(imageRes.format, info.format);
            Assert.assertEquals(imageRes.opaque, info.opaque);
            Assert.assertEquals(imageRes.frameCount, info.frameCount);
        }
    }

    private void testBitmapDecoderDecode() {
        for (ImageRes imageRes : IMAGES) {
            if (imageRes.format == Image.FORMAT_GIF) {
                // Skip gif
                continue;
            }
            Log.d(LOG_TAG, "testBitmapDecoderDecode " + mResources.getResourceEntryName(imageRes.id));
            for (int config : CONFIGS) {
                for (int ratio : RATIOS) {
                    testBitmapDecoderDecode1(imageRes.id, config, ratio);
                }
            }
        }
    }

    private String getBitmapDecoderDecodeName(int id, int config, int ratio) {
        final String idName = mResources.getResourceEntryName(id);
        final String configName = configToString(config);
        return "bitmap_decoder_" + idName + "_" + configName + "_ratio-" + ratio;
    }

    private void testBitmapDecoderDecode1(int id, int config, int ratio) {
        final String name = getBitmapDecoderDecodeName(id, config, ratio);
        final Bitmap bitmap = BitmapDecoder.decode(mResources.openRawResource(id), config, ratio);
        Assert.assertNotNull(bitmap);

        try {
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, new FileOutputStream(new File(mDir, name + ".png")));
        } catch (FileNotFoundException e) {
            Log.e(LOG_TAG, "Can't save BitmapDecoder decode " + name, e);
        }
        bitmap.recycle();
    }

    private String rectToString(Rect rect) {
        return rect.left + "_" + rect.top + "_" + rect.right + "_" + rect.bottom;
    }

    private String getBitmapRegionDecoderDecodeName(int id, Rect rect, int config, int ratio) {
        final String idName = mResources.getResourceEntryName(id);
        final String configName = configToString(config);
        return "bitmap_region_decoder_" + idName + "_" + rectToString(rect) + "_"+ configName + "_ratio-" + ratio;
    }

    private void testBitmapRegionDecoderDecode() {
        for (ImageRes imageRes : IMAGES) {
            if (imageRes.format == Image.FORMAT_GIF) {
                // Skip gif
                continue;
            }

            Log.d(LOG_TAG, "testBitmapRegionDecoderDecode " + mResources.getResourceEntryName(imageRes.id));

            final BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(mResources.openRawResource(imageRes.id));
            Assert.assertNotNull(decoder);
            Assert.assertEquals(imageRes.width, decoder.getWidth());
            Assert.assertEquals(imageRes.height, decoder.getHeight());
            Assert.assertEquals(imageRes.format, decoder.getFormat());
            Assert.assertEquals(imageRes.opaque, decoder.isOpaque());

            final int h1 = decoder.getWidth() / 4;
            final int h2 = decoder.getWidth() / 3;
            final int v1 = decoder.getHeight() / 4;
            final int v2 = decoder.getHeight() / 3;

            final Rect rect = new Rect();
            for (int config : CONFIGS) {
                for (int ratio : RATIOS) {
                    for (int left = 0; left < decoder.getWidth(); left += MathUtils.random(h1, h2)) {
                        for (int right = left + MathUtils.random(h1, h2); right < decoder.getWidth(); right += MathUtils.random(h1, h2)) {
                            for (int top = 0; top < decoder.getHeight(); top += MathUtils.random(v1, v2)) {
                                for (int bottom = top + MathUtils.random(v1, v2); bottom < decoder.getHeight(); bottom += MathUtils.random(v1, v2)) {
                                    rect.set(left, top, right, bottom);
                                    testBitmapRegionDecoderDecode1(imageRes.id, decoder, rect, config, ratio);
                                }
                            }
                        }
                    }
                }
            }

            decoder.recycle();
            Log.i(LOG_TAG, "" + FileUtils.readableByteCount(Debug.getNativeHeapAllocatedSize(), false));
        }
    }

    private void testBitmapRegionDecoderDecode1(int id, BitmapRegionDecoder decoder, Rect rect, int config, int ratio) {
        final String name = getBitmapRegionDecoderDecodeName(id, rect, config, ratio);
        final Bitmap bitmap = decoder.decodeRegion(rect, config, ratio);
        Assert.assertNotNull(bitmap);

        try {
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, new FileOutputStream(new File(mDir, name + ".png")));
        } catch (FileNotFoundException e) {
            Log.e(LOG_TAG, "Can't save BitmapDecoder decode " + name, e);
        }
        bitmap.recycle();
    }
}


code:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.hippo.image.example.MainActivity">

    <ImageView
        android:id="@+id/image"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</RelativeLayout>


code:
<resources>
    <!-- Example customization of dimensions originally defined in res/values/dimens.xml
         (such as screen margins) for screens with more than 820dp of available width. This
         would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
    <dimen name="activity_horizontal_margin">64dp</dimen>
</resources>


code:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>
</resources>


code:
<resources>
    <!-- Default screen margins, per the Android Design guidelines. -->
    <dimen name="activity_horizontal_margin">16dp</dimen>
    <dimen name="activity_vertical_margin">16dp</dimen>
</resources>


code:
<resources>
    <string name="app_name">Image</string>
</resources>


code:
<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>


code:
package com.hippo.image.example;

import org.junit.Test;

import static org.junit.Assert.*;

/**
 * To work on unit tests, switch the Test Artifact in the Build Variants view.
 */
public class ExampleUnitTest {
    @Test
    public void addition_isCorrect() throws Exception {
        assertEquals(4, 2 + 2);
    }
}


code:
// Top-level build file where you can add configuration options common to all sub-projects/modules.

project.ext {
    compileSdkVersion = 27
    buildToolsVersion = '29.0.3'

    minSdkVersion = 21
    targetSdkVersion = 27
    versionCode = 30
    versionName = "0.3.0"

    supportLibraryVersion = '27.1.1'
    konwidgetVersion = '0.1.0'
    yorozuyaVersion = '0.1.3'
    junitVersion = '4.12'
    androidTestRunnerVersion = '1.0.2'
    jsoupVersion = '1.11.3'
}

buildscript {
    repositories {
        jcenter()
        google()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:3.2.1'
        classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        jcenter()
        google()
        maven { url "https://jitpack.io" }
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}


code:
# Project-wide Gradle settings.

# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.

# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html

# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8

# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true


code:
#!/usr/bin/env bash

##############################################################################
##
##  Gradle start up script for UN*X
##
##############################################################################

# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""

APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"

warn ( ) {
    echo "$*"
}

die ( ) {
    echo
    echo "$*"
    echo
    exit 1
}

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
  CYGWIN* )
    cygwin=true
    ;;
  Darwin* )
    darwin=true
    ;;
  MINGW* )
    msys=true
    ;;
esac

# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
    ls=`ls -ld "$PRG"`
    link=`expr "$ls" : '.*-> \(.*\)$'`
    if expr "$link" : '/.*' > /dev/null; then
        PRG="$link"
    else
        PRG=`dirname "$PRG"`"/$link"
    fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null

CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar

# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
        # IBM's JDK on AIX uses strange locations for the executables
        JAVACMD="$JAVA_HOME/jre/sh/java"
    else
        JAVACMD="$JAVA_HOME/bin/java"
    fi
    if [ ! -x "$JAVACMD" ] ; then
        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
    fi
else
    JAVACMD="java"
    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
    MAX_FD_LIMIT=`ulimit -H -n`
    if [ $? -eq 0 ] ; then
        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
            MAX_FD="$MAX_FD_LIMIT"
        fi
        ulimit -n $MAX_FD
        if [ $? -ne 0 ] ; then
            warn "Could not set maximum file descriptor limit: $MAX_FD"
        fi
    else
        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
    fi
fi

# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi

# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
    JAVACMD=`cygpath --unix "$JAVACMD"`

    # We build the pattern for arguments to be converted via cygpath
    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
    SEP=""
    for dir in $ROOTDIRSRAW ; do
        ROOTDIRS="$ROOTDIRS$SEP$dir"
        SEP="|"
    done
    OURCYGPATTERN="(^($ROOTDIRS))"
    # Add a user-defined pattern to the cygpath arguments
    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
    fi
    # Now convert the arguments - kludge to limit ourselves to /bin/sh
    i=0
    for arg in "$@" ; do
        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option

        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
        else
            eval `echo args$i`="\"$arg\""
        fi
        i=$((i+1))
    done
    case $i in
        (0) set -- ;;
        (1) set -- "$args0" ;;
        (2) set -- "$args0" "$args1" ;;
        (3) set -- "$args0" "$args1" "$args2" ;;
        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
    esac
fi

# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
    JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"

exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

path: gradlew


code:
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem  Gradle startup script for Windows
@rem
@rem ##########################################################################

@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal

@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=

set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init

echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe

if exist "%JAVA_EXE%" goto init

echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:init
@rem Get command-line arguments, handling Windowz variants

if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args

:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2

:win9xME_args_slurp
if "x%~1" == "x" goto execute

set CMD_LINE_ARGS=%*
goto execute

:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$

:execute
@rem Setup the command line

set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd

:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1

:mainEnd
if "%OS%"=="Windows_NT" endlocal

:omega


code:
apply plugin: 'com.android.library'

android {
    compileSdkVersion rootProject.ext.compileSdkVersion
    buildToolsVersion rootProject.ext.buildToolsVersion

    sourceSets {
        main {
            java.srcDirs = ['../src/main/java']
            manifest.srcFile '../src/main/AndroidManifest.xml'
        }
    }

    defaultConfig {
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode rootProject.ext.versionCode
        versionName rootProject.ext.versionName

        externalNativeBuild {
            cmake {
                targets "image"
                arguments "-DANDROID_ARM_NEON=TRUE",
                          "-DIMAGE_SINGLE_SHARED_LIB=FALSE"
            }
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), '../proguard-rules.pro'
        }
    }

    externalNativeBuild {
        cmake {
            path '../src/main/jni/CMakeLists.txt'
        }
    }
}

dependencies {
    implementation "com.android.support:support-annotations:${rootProject.ext.supportLibraryVersion}"
    testImplementation "junit:junit:${rootProject.ext.junitVersion}"
}

project.afterEvaluate {
    externalNativeBuildDebug.dependsOn ':singlelib:javah'
    externalNativeBuildRelease.dependsOn ':singlelib:javah'
}

// Publish arr with java source and java doc
apply from: rootProject.file('android-maven-gradle.gradle')


code:
apply plugin: 'com.android.library'

android {
    compileSdkVersion rootProject.ext.compileSdkVersion
    buildToolsVersion rootProject.ext.buildToolsVersion

    sourceSets {
        main {
            java.srcDirs = []
            manifest.srcFile 'AndroidManifest.xml'
        }
    }

    packagingOptions {
        // Do not include libimage.so in the codec aar
        exclude '**/libimage.so'
    }

    defaultConfig {
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode rootProject.ext.versionCode
        versionName rootProject.ext.versionName

        externalNativeBuild {
            cmake {
                targets "image-gif"
                arguments "-DANDROID_ARM_NEON=TRUE",
                          "-DIMAGE_SINGLE_SHARED_LIB=FALSE"
            }
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), '../proguard-rules.pro'
        }
    }

    externalNativeBuild {
        cmake {
            path '../src/main/jni/CMakeLists.txt'
        }
    }
}

project.afterEvaluate {
    externalNativeBuildDebug.dependsOn ':singlelib:javah'
    externalNativeBuildRelease.dependsOn ':singlelib:javah'
}

// Publish arr without java source and java doc
apply plugin: 'com.github.dcendents.android-maven'


code:
apply plugin: 'com.android.library'

android {
    compileSdkVersion rootProject.ext.compileSdkVersion
    buildToolsVersion rootProject.ext.buildToolsVersion

    sourceSets {
        main {
            java.srcDirs = []
            manifest.srcFile 'AndroidManifest.xml'
        }
    }

    packagingOptions {
        // Do not include libimage.so in the codec aar
        exclude '**/libimage.so'
    }

    defaultConfig {
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode rootProject.ext.versionCode
        versionName rootProject.ext.versionName

        externalNativeBuild {
            cmake {
                targets "image-jpeg"
                arguments "-DIMAGE_SINGLE_SHARED_LIB=FALSE"
            }
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), '../proguard-rules.pro'
        }
    }

    externalNativeBuild {
        cmake {
            path '../src/main/jni/CMakeLists.txt'
        }
    }
}

project.afterEvaluate {
    externalNativeBuildDebug.dependsOn ':singlelib:javah'
    externalNativeBuildRelease.dependsOn ':singlelib:javah'
}

// Publish arr without java source and java doc
apply plugin: 'com.github.dcendents.android-maven'


code:
apply plugin: 'com.android.library'

android {
    compileSdkVersion rootProject.ext.compileSdkVersion
    buildToolsVersion rootProject.ext.buildToolsVersion

    sourceSets {
        main {
            java.srcDirs = []
            manifest.srcFile 'AndroidManifest.xml'
        }
    }

    packagingOptions {
        // Do not include libimage.so in the codec aar
        exclude '**/libimage.so'
    }

    defaultConfig {
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode rootProject.ext.versionCode
        versionName rootProject.ext.versionName

        externalNativeBuild {
            cmake {
                targets "image-png"
                arguments "-DANDROID_ARM_NEON=TRUE",
                          "-DIMAGE_SINGLE_SHARED_LIB=FALSE"
            }
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), '../proguard-rules.pro'
        }
    }

    externalNativeBuild {
        cmake {
            path '../src/main/jni/CMakeLists.txt'
        }
    }
}

project.afterEvaluate {
    externalNativeBuildDebug.dependsOn ':singlelib:javah'
    externalNativeBuildRelease.dependsOn ':singlelib:javah'
}

// Publish arr without java source and java doc
apply plugin: 'com.github.dcendents.android-maven'


code:
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in E:\Android\android-sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}


code:
import org.apache.tools.ant.taskdefs.condition.Os

apply plugin: 'com.android.library'

android {
    compileSdkVersion rootProject.ext.compileSdkVersion
    buildToolsVersion rootProject.ext.buildToolsVersion

    sourceSets {
        main {
            java.srcDirs = ['../src/main/java']
            manifest.srcFile '../src/main/AndroidManifest.xml'
        }
    }

    defaultConfig {
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode rootProject.ext.versionCode
        versionName rootProject.ext.versionName

        externalNativeBuild {
            cmake {
                targets "image"
                arguments "-DANDROID_ARM_NEON=TRUE",
                          "-DIMAGE_SINGLE_SHARED_LIB=TRUE"
            }
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), '../proguard-rules.pro'
        }
    }

    externalNativeBuild {
        cmake {
            path '../src/main/jni/CMakeLists.txt'
        }
    }
}

clean {
    delete '../src/main/jni/image/javah'
}

task javah(type: Exec, dependsOn: 'compileReleaseJavaWithJavac') {
    char separator = Os.isFamily(Os.FAMILY_WINDOWS) ? ';' : ':'
    commandLine 'javah', '-jni',
            '-d', '../src/main/jni/image/javah',
            '-cp', "${android.getSdkDirectory().getAbsolutePath()}/platforms/android-${rootProject.ext.targetSdkVersion}/android.jar${separator}build/intermediates/javac/release/compileReleaseJavaWithJavac/classes",
            'com.hippo.image.Image',
            'com.hippo.image.StaticImage',
            'com.hippo.image.StaticDelegateImage',
            'com.hippo.image.AnimatedImage',
            'com.hippo.image.AnimatedDelegateImage',
            'com.hippo.image.BitmapDecoder',
            'com.hippo.image.BitmapRegionDecoder'
    inputs.dir('../src/main/java')
    outputs.dir('../src/main/jni/image/javah')
}

dependencies {
    implementation "com.android.support:support-annotations:${rootProject.ext.supportLibraryVersion}"
    testImplementation "junit:junit:${rootProject.ext.junitVersion}"
}

project.afterEvaluate {
    externalNativeBuildDebug.dependsOn javah
    externalNativeBuildRelease.dependsOn javah
}

// Publish arr with java source and java doc
apply from: rootProject.file('android-maven-gradle.gradle')


code:
package com.hippo.image;

import android.app.Application;
import android.test.ApplicationTestCase;

/**
 * Testing Fundamentals
 */
public class ApplicationTest extends ApplicationTestCase<Application> {
    public ApplicationTest() {
        super(Application.class);
    }
}


code:
/*
 * Copyright 2018 Hippo Seven
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.hippo.image;

import android.support.test.InstrumentationRegistry;
import java.io.File;
import java.io.IOException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.junit.Test;

public class NativeTest {

  @Test
  public void testNative() throws IOException {
    File tempDir = InstrumentationRegistry.getContext().getCacheDir();
    File logFile = new File(tempDir, "native_test.xml");

    int failed = nativeTestNative(tempDir.getAbsolutePath(), logFile.getAbsolutePath());

    if (failed != 0) {
      String log = readXmlLog(logFile);
      if (log.isEmpty()) {
        throw new IllegalStateException(failed + " test(s) failed, but no log.");
      } else {
        throw new IllegalStateException(failed + " test(s) failed\n\n" + readXmlLog(logFile));
      }
    }
  }

  private String readXmlLog(File logFile) throws IOException {
    StringBuilder builder = new StringBuilder();

    Document document = Jsoup.parse(logFile, "utf-8");

    Elements failures = document.getElementsByAttributeValue("result", "failure");
    for (Element failure : failures) {
      builder.append(failure.getElementsByTag("id").get(0).text()).append("\n");
      builder.append(failure.getElementsByTag("fn").get(0).text()).append("\n");
      builder.append(failure.getElementsByTag("message").get(0).text()).append("\n");
      builder.append("\n");
    }

    Elements errors = document.getElementsByAttributeValue("result", "error");
    for (Element error : errors) {
      builder.append(error.getElementsByTag("id").get(0).text()).append("\n");
      builder.append(error.getElementsByTag("fn").get(0).text()).append("\n");
      builder.append(error.getElementsByTag("message").get(0).text()).append("\n");
      builder.append("\n");
    }

    return builder.toString();
  }

  static {
    System.loadLibrary("image-test");
  }

  private static native int nativeTestNative(String tempDir, String logFile);
}


code: <manifest package="com.hippo.image"/>


code:
/*
 * Copyright 2016 Hippo Seven
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.hippo.image;

/*
 * Created by Hippo on 8/6/2016.
 */

import android.graphics.Bitmap;
import android.support.annotation.NonNull;

final class AnimatedDelegateImage implements ImageRenderer {

    private final AnimatedImage mAnimatedImage;
    private long mNativePtr;

    AnimatedDelegateImage(AnimatedImage animatedImage) {
        mAnimatedImage = animatedImage;
        mNativePtr = nativeNew(animatedImage.getWidth(), animatedImage.getHeight());
        if (mNativePtr == 0) {
            throw new IllegalStateException("Can't new AnimatedDelegateImage data");
        }
        // To render first frame
        reset();

        // For statistics
        ++Image.mImageRendererCount;
    }

    @Override
    public void recycle() {
        if (mNativePtr != 0) {
            mAnimatedImage.removeReference();
            nativeRecycle(mNativePtr);
            mNativePtr = 0;

            // For statistics
            --Image.mImageRendererCount;
        }
    }

    @Override
    public boolean isRecycled() {
        return mNativePtr == 0;
    }

    @NonNull
    @Override
    public ImageData getImageData() {
        return mAnimatedImage;
    }

    // Throw IllegalStateException if recycled
    private void checkRecycled(String errorMessage) {
        if (mNativePtr == 0) {
            throw new IllegalStateException(errorMessage);
        }
    }

    @Override
    public int getCurrentDelay() {
        checkRecycled("Can't call getCurrentDelay on recycled ImageRender");
        return nativeGetCurrentDelay(mNativePtr, mAnimatedImage.getNativePtr());
    }

    @Override
    public void render(Bitmap bitmap, int dstX, int dstY, int srcX, int srcY,
            int width, int height, int ratio, boolean fillBlank, int fillColor) {
        checkRecycled("Can't call render on recycled ImageRender");
        nativeRender(mNativePtr, bitmap, dstX, dstY,
                srcX, srcY, width, height, ratio, fillBlank, fillColor);
    }

    @Override
    public void glTex(boolean init, int texW, int texH,
            int dstX, int dstY, int srcX, int srcY,
            int width, int height, int ratio) {
        checkRecycled("Can't glTex render on recycled ImageRender");
        nativeGlTex(mNativePtr, Image.getBufferPtr(texW * texH),
                init, texW, texH, dstX, dstY, srcX, srcY, width, height, ratio);
    }

    @Override
    public void advance() {
        checkRecycled("Can't call advance on recycled ImageRender");
        nativeAdvance(mNativePtr, mAnimatedImage.getNativePtr());
    }

    @Override
    public void reset() {
        checkRecycled("Can't call reset on recycled ImageRender");
        nativeReset(mNativePtr, mAnimatedImage.getNativePtr());
    }

    @Override
    protected void finalize() throws Throwable {
        try {
            if (mNativePtr != 0) {
                nativeRecycle(mNativePtr);
                mNativePtr = 0;
            }
        } finally {
            super.finalize();
        }
    }

    private static native long nativeNew(int width, int height);

    private static native void nativeRecycle(long nativePtr);

    private static native int nativeGetCurrentDelay(long render, long data);

    private static native void nativeRender(long nativePtr,
            Bitmap bitmap, int dstX, int dstY, int srcX, int srcY,
            int width, int height, int ratio, boolean fillBlank, int fillColor);

    private static native void nativeGlTex(long nativePtr, long bufferPtr,
            boolean init, int texW, int texH, int dstX, int dstY,
            int srcX, int srcY, int width, int height, int ratio);

    private static native void nativeAdvance(long render, long data);

    private static native void nativeReset(long render, long data);
}


code:
/*
 * Copyright 2016 Hippo Seven
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.hippo.image;

/*
 * Created by Hippo on 8/4/2016.
 */

import android.support.annotation.Keep;
import android.support.annotation.NonNull;

@Keep
final class AnimatedImage implements ImageData {

    private long mNativePtr;
    private final int mWidth;
    private final int mHeight;
    private final int mFormat;
    private final boolean mOpaque;

    private int mFrameCount;
    private int[] mDelayArray;
    private int mByteCount;

    private boolean mCompleted;

    private int mReference;
    private boolean mAutomatic = true;

    private boolean mBrowserCompat;

    private AnimatedImage(long nativePtr, int width, int height, int format, boolean opaque) {
        mNativePtr = nativePtr;
        mWidth = width;
        mHeight = height;
        mFormat = format;
        mOpaque = opaque;
        mCompleted = false;

        // For statistics
        ++Image.mImageDataCount;
    }

    // Called in complete native
    private void onComplete(int frameCount, int[] delayArray, int byteCount) {
        mFrameCount = frameCount;
        mDelayArray = delayArray;
        mByteCount = byteCount;
        mCompleted = true;
    }

    @Override
    public void recycle() {
        // Check reference
        if (mReference > 0) {
            throw new IllegalStateException("Can't release referenced ImageData");
        }
        // Release native data
        if (mNativePtr != 0) {
            nativeRecycle(mNativePtr);
            mNativePtr = 0;

            // For statistics
            --Image.mImageDataCount;
        }
    }

    @Override
    public boolean isRecycled() {
        return mNativePtr == 0;
    }

    @Override
    public void setAutomatic(boolean automatic) {
        mAutomatic = automatic;
    }

    @Override
    public boolean isAutomatic() {
        return mAutomatic;
    }

    // Throw IllegalStateException if recycled
    private void checkRecycled(String errorMessage) {
        if (mNativePtr == 0) {
            throw new IllegalStateException(errorMessage);
        }
    }

    // Return native ptr
    long getNativePtr() {
        checkRecycled("Can't use recycled ImageData");
        return mNativePtr;
    }

    @NonNull
    @Override
    public ImageRenderer createImageRenderer() {
        checkRecycled("Can't create ImageRenderer on recycled ImageData");
        ++mReference;
        return new AnimatedDelegateImage(this);
    }

    @Override
    public boolean isReferenced() {
        return mReference != 0;
    }

    @Override
    public void addReference() {
        ++mReference;
    }

    @Override
    public void removeReference() {
        --mReference;
        // Check reference valid
        if (mReference < 0) {
            throw new IllegalStateException("Can't remove reference from a non-referenced ImageData.");
        }
        // Check automatic
        if (mReference == 0 && mAutomatic) {
            recycle();
        }
    }

    @Override
    public void complete() {
        if (mNativePtr != 0 && !mCompleted) {
            nativeComplete(this, mNativePtr);
        }
    }

    @Override
    public boolean isCompleted() {
        return mCompleted;
    }

    @Override
    public int getWidth() {
        return mWidth;
    }

    @Override
    public int getHeight() {
        return mHeight;
    }

    @Override
    public int getFormat() {
        return mFormat;
    }

    @Override
    public boolean isOpaque() {
        return mOpaque;
    }

    @Override
    public int getFrameCount() {
        if (mCompleted) {
            return mFrameCount;
        } else {
            throw new IllegalStateException("Can't get frame count on Uncompleted animated image");
        }
    }

    @Override
    public int getDelay(int frame) {
        if (mCompleted) {
            int delay = mDelayArray[frame];
            return mBrowserCompat && delay <= 10 ? 100 : delay;
        } else {
            throw new IllegalStateException("Can't get delay on Uncompleted animated image");
        }
    }

    @Override
    public int getByteCount() {
        if (mCompleted) {
            return mByteCount;
        } else {
            throw new IllegalStateException("Can't get byte count on Uncompleted animated image");
        }
    }

    @Override
    public void setBrowserCompat(boolean enable) {
        mBrowserCompat = enable;
    }

    @Override
    public boolean isBrowserCompat() {
        return mBrowserCompat;
    }

    @Override
    protected void finalize() throws Throwable {
        try {
            if (mNativePtr != 0) {
                nativeRecycle(mNativePtr);
                mNativePtr = 0;
            }
        } finally {
            super.finalize();
        }
    }

    private static native void nativeRecycle(long nativePtr);

    private static native void nativeComplete(AnimatedImage image, long nativePtr);
}


code:
/*
 * Copyright 2016 Hippo Seven
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.hippo.image;

/*
 * Created by Hippo on 9/12/2016.
 */

import android.graphics.Bitmap;
import android.support.annotation.IntDef;
import android.support.annotation.Keep;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;

import java.io.InputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

public final class BitmapDecoder {
    private BitmapDecoder() {}

    private static final String LOG_TAG = BitmapDecoder.class.getSimpleName();

    @IntDef({CONFIG_AUTO, CONFIG_RGB_565, CONFIG_RGBA_8888})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Config {}

    /**
     * The same as {@link #CONFIG_RGB_565} if the image is opaque,
     * otherwise {@link #CONFIG_RGBA_8888}
     */
    public static final int CONFIG_AUTO = 0;
    /**
     * The same as {@link android.graphics.Bitmap.Config#RGB_565}.
     */
    public static final int CONFIG_RGB_565 = 1;
    /**
     * The same as {@link android.graphics.Bitmap.Config#ARGB_8888}.
     */
    public static final int CONFIG_RGBA_8888 = 2;

    /**
     * Only decode image info.
     *
     * @param is The image source.
     * @param info The struct to set image info.
     * @return Return {@code true} if
     */
    public static boolean decode(InputStream is, @NonNull ImageInfo info) {
        return nativeDecodeInfo(is, info);
    }

    /**
     * config is {@code CONFIG_AUTO}.
     * ratio is {@code 1}.
     *
     * @see #decode(InputStream, int, int)
     */
    @Nullable
    public static Bitmap decode(InputStream is) {
        return nativeDecodeBitmap(is, CONFIG_AUTO, 1);
    }

    /**
     * ratio is {@code 1}.
     *
     * @see #decode(InputStream, int, int)
     */
    @Nullable
    public static Bitmap decode(InputStream is, @Config int config) {
        return nativeDecodeBitmap(is, config, 1);
    }

    /**
     * Decode bitmap from {@code InputStream}. Return {@code null} if out of memory.
     *
     * @param is The image source.
     * @param config One of {@link #CONFIG_AUTO}, {@link #CONFIG_RGB_565} and {@link #CONFIG_RGBA_8888}
     * @param ratio If set to a value > 1, requests the decoder to subsample the original.
     *               image, returning a smaller image to save memory. Power of 2 is not necessary.
     * @return The decoded bitmap, or null if the image data could not be
     *         decoded.
     */
    @Nullable
    public static Bitmap decode(InputStream is, @Config int config, int ratio) {
        return nativeDecodeBitmap(is, config, ratio);
    }

    // For native code
    @Keep
    private static Bitmap createBitmap(int width, int height, int config) {
        final Bitmap.Config conf;
        switch (config) {
            case CONFIG_RGB_565:
                conf = Bitmap.Config.RGB_565;
                break;
            case CONFIG_RGBA_8888:
                conf = Bitmap.Config.ARGB_8888;
                break;
            default:
                Log.e(LOG_TAG, "Can't convert this config to Bitmap.Config: " + config);
                return null;
        }

        try {
            return Bitmap.createBitmap(width, height, conf);
        } catch (OutOfMemoryError e) {
            Log.e(LOG_TAG, "Failed to create bitmap: width = " + width + ", height = " + height + ", config = " + conf, e);
            return null;
        }
    }

    static {
        System.loadLibrary("image");
    }

    private static native boolean nativeDecodeInfo(InputStream is, ImageInfo info);

    private static native Bitmap nativeDecodeBitmap(InputStream is, int config, int ratio);
}