// diffColors compares two color values and returns a color to indicate the // difference. If the colors differ it updates maxRGBADiffs to contain the // maximum difference over multiple calls. // If the RGB channels are identical, but the alpha differ then // PixelAlphaDiffColor is returned. This allows to distinguish pixels that // render the same, but have different alpha values. func diffColors(color1, color2 color.Color, maxRGBADiffs []int) color.Color { // We compare them before normalizing to non-premultiplied. If one of the // original images did not have an alpha channel (but the other did) the // equality will be false. if color1 == color2 { return PixelMatchColor } // Treat all colors as non-premultiplied. c1 := color.NRGBAModel.Convert(color1).(color.NRGBA) c2 := color.NRGBAModel.Convert(color2).(color.NRGBA) rDiff := util.AbsInt(int(c1.R) - int(c2.R)) gDiff := util.AbsInt(int(c1.G) - int(c2.G)) bDiff := util.AbsInt(int(c1.B) - int(c2.B)) aDiff := util.AbsInt(int(c1.A) - int(c2.A)) maxRGBADiffs[0] = util.MaxInt(maxRGBADiffs[0], rDiff) maxRGBADiffs[1] = util.MaxInt(maxRGBADiffs[1], gDiff) maxRGBADiffs[2] = util.MaxInt(maxRGBADiffs[2], bDiff) maxRGBADiffs[3] = util.MaxInt(maxRGBADiffs[3], aDiff) // If the color channels differ we mark with the diff color. if (c1.R != c2.R) || (c1.G != c2.G) || (c1.B != c2.B) { // We use the Manhattan metric for color difference. return uint8ToColor(PixelDiffColor[deltaOffset(rDiff+gDiff+bDiff+aDiff)]) } // If only the alpha channel differs we mark it with the alpha diff color. // if aDiff > 0 { return uint8ToColor(PixelAlphaDiffColor[deltaOffset(aDiff)]) } return PixelMatchColor }
func (b *Blamer) getBlame(blameDistribution *BlameDistribution, blameCommits, commits []*tiling.Commit) ([]int, int) { if (blameDistribution == nil) || (len(blameDistribution.Freq) == 0) { return []int{}, 0 } // We have a blamelist. Let's find the indices relative to the given // list of commits. freq := blameDistribution.Freq ret := make([]int, 0, len(freq)) maxCount := util.MaxInt(freq...) // Find the first element in the list and align the commits. idx := 0 for freq[idx] < maxCount { idx++ } tgtCommit := blameCommits[len(blameCommits)-len(freq)+idx] commitIdx := sort.Search(len(commits), func(i int) bool { return commits[i].CommitTime >= tgtCommit.CommitTime }) for (idx < len(freq)) && (freq[idx] > 0) && (commitIdx < len(commits)) { ret = append(ret, commitIdx) idx++ commitIdx++ } return ret, maxCount }
func (h *historian) backFillDigestInfo(tilesToBackfill int) { go func() { // Get the first tile and determine the tile id of the first tile var err error lastTile, err := h.storages.TileStore.Get(0, -1) if err != nil { glog.Errorf("Unable to retrieve last tile. Quiting backfill. Error: %s", err) return } var tile *tiling.Tile firstTileIndex := util.MaxInt(lastTile.TileIndex-tilesToBackfill+1, 0) for idx := firstTileIndex; idx <= lastTile.TileIndex; idx++ { if tile, err = h.storages.TileStore.Get(0, idx); err != nil { glog.Errorf("Unable to read tile %d from tile store. Quiting backfill. Error: %s", idx, err) return } // Process the tile, but giving higher priority to digests from the // latest tile. if err = h.processTile(tile); err != nil { glog.Errorf("Error processing tile: %s", err) } // Read the last tile, just to make sure it has not changed. if lastTile, err = h.storages.TileStore.Get(0, -1); err != nil { glog.Errorf("Unable to retrieve last tile. Quiting backfill. Error: %s", err) return } } }() }
// Diff is a utility function that calculates the DiffMetrics and the image of the // difference for the provided images. func Diff(img1, img2 image.Image) (*DiffMetrics, *image.NRGBA) { img1Bounds := img1.Bounds() img2Bounds := img2.Bounds() // Get the bounds we want to compare. cmpWidth := util.MinInt(img1Bounds.Dx(), img2Bounds.Dx()) cmpHeight := util.MinInt(img1Bounds.Dy(), img2Bounds.Dy()) // Get the bounds of the resulting image. If they dimensions match they // will be identical to the result bounds. Fill the image with black pixels. resultWidth := util.MaxInt(img1Bounds.Dx(), img2Bounds.Dx()) resultHeight := util.MaxInt(img1Bounds.Dy(), img2Bounds.Dy()) resultImg := image.NewNRGBA(image.Rect(0, 0, resultWidth, resultHeight)) totalPixels := resultWidth * resultHeight // Loop through all points and compare. We start assuming all pixels are // wrong. This takes care of the case where the images have different sizes // and there is an area not inspected by the loop. numDiffPixels := resultWidth * resultHeight maxRGBADiffs := make([]int, 4) // Pix is a []uint8 rotating through R, G, B, A, R, G, B, A, ... p1 := GetNRGBA(img1).Pix p2 := GetNRGBA(img2).Pix // Compare the bounds, if they are the same then use this fast path. // We pun to uint64 to compare 2 pixels at a time, so we also require // an even number of pixels here. If that's a big deal, we can easily // fix that up, handling the straggler pixel separately at the end. if img1Bounds.Eq(img2Bounds) && len(p1)%8 == 0 { numDiffPixels = 0 // Note the += 8. We're checking two pixels at a time here. for i := 0; i < len(p1); i += 8 { // Most pixels we compare will be the same, so from here to // the 'continue' is the hot path in all this code. rgba_2x := (*uint64)(unsafe.Pointer(&p1[i])) RGBA_2x := (*uint64)(unsafe.Pointer(&p2[i])) if *rgba_2x == *RGBA_2x { continue } // When off == 0, we check the first pixel of the pair; when 4, the second. for off := 0; off <= 4; off += 4 { r, g, b, a := p1[off+i+0], p1[off+i+1], p1[off+i+2], p1[off+i+3] R, G, B, A := p2[off+i+0], p2[off+i+1], p2[off+i+2], p2[off+i+3] if r != R || g != G || b != B || a != A { numDiffPixels++ dr := util.AbsInt(int(r) - int(R)) dg := util.AbsInt(int(g) - int(G)) db := util.AbsInt(int(b) - int(B)) da := util.AbsInt(int(a) - int(A)) maxRGBADiffs[0] = util.MaxInt(dr, maxRGBADiffs[0]) maxRGBADiffs[1] = util.MaxInt(dg, maxRGBADiffs[1]) maxRGBADiffs[2] = util.MaxInt(db, maxRGBADiffs[2]) maxRGBADiffs[3] = util.MaxInt(da, maxRGBADiffs[3]) if dr+dg+db > 0 { copy(resultImg.Pix[off+i:], PixelDiffColor[deltaOffset(dr+dg+db+da)]) } else { copy(resultImg.Pix[off+i:], PixelAlphaDiffColor[deltaOffset(da)]) } } } } } else { for x := 0; x < cmpWidth; x++ { for y := 0; y < cmpHeight; y++ { color1 := img1.At(x, y) color2 := img2.At(x, y) dc := diffColors(color1, color2, maxRGBADiffs) if dc == PixelMatchColor { numDiffPixels-- } resultImg.Set(x, y, dc) } } } return &DiffMetrics{ NumDiffPixels: numDiffPixels, PixelDiffPercent: getPixelDiffPercent(numDiffPixels, totalPixels), MaxRGBADiffs: maxRGBADiffs, DimDiffer: (cmpWidth != resultWidth) || (cmpHeight != resultHeight)}, resultImg }
// GetLastTrimmed returns the last tile as read-only trimmed to contain at // most NCommits. It caches trimmed tiles as long as the underlying tiles // do not change. // // includeIgnores - If true then include ignored digests in the returned tile. func (s *Storage) GetLastTileTrimmed(includeIgnores bool) (*tiling.Tile, error) { // Get the last (potentially cached) tile. tile, err := s.TileStore.Get(0, -1) if err != nil { return nil, err } s.mutex.Lock() defer s.mutex.Unlock() if s.NCommits <= 0 { return tile, err } currentIgnoreRev := s.IgnoreStore.Revision() // Check if the tile hasn't changed and the ignores haven't changed. if tile == s.lastBaseTile && s.lastTrimmedTile != nil && s.lastTrimmedIgnoredTile != nil && currentIgnoreRev == s.lastIgnoreRev { if includeIgnores { return s.lastTrimmedTile, nil } else { return s.lastTrimmedIgnoredTile, nil } } ignores, err := s.IgnoreStore.List() if err != nil { return nil, fmt.Errorf("Failed to get ignores to filter tile: %s", err) } // Build a new trimmed tile and a new trimmed tile with all ingoreable traces removed. tileLen := tile.LastCommitIndex() + 1 // First build the new trimmed tile. retTile, err := tile.Trim(util.MaxInt(0, tileLen-s.NCommits), tileLen) if err != nil { return nil, err } // Now copy the tile by value. retIgnoredTile := retTile.Copy() // Then remove traces that should be ignored. ignoreQueries, err := ignore.ToQuery(ignores) if err != nil { return nil, err } for id, tr := range retIgnoredTile.Traces { for _, q := range ignoreQueries { if tiling.Matches(tr, q) { delete(retIgnoredTile.Traces, id) continue } } } // Cache this tile. s.lastIgnoreRev = currentIgnoreRev s.lastTrimmedTile = retTile s.lastTrimmedIgnoredTile = retIgnoredTile s.lastBaseTile = tile fmt.Printf("Lengths: %d %d\n", len(s.lastTrimmedTile.Traces), len(s.lastTrimmedIgnoredTile.Traces)) if includeIgnores { return s.lastTrimmedTile, nil } else { return s.lastTrimmedIgnoredTile, nil } }